Explorar el Código

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

wjx hace 4 meses
padre
commit
e6361619a9

+ 42 - 0
src/components/Settings/index.tsx

@@ -0,0 +1,42 @@
+import { FullscreenExitOutlined, FullscreenOutlined, RedoOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons";
+import { Button, Col, Row, Space, Tooltip } from "antd";
+import React, { useContext } from "react";
+import { DispatchHeader } from "../TablePro";
+
+/**
+ * 头部
+ * @returns 
+ */
+const Settings: React.FC<{ leftChild?: JSX.Element; czChild?: JSX.Element; ajax?: any; }> = ({ leftChild, czChild, ajax }) => {
+
+    /******************************/
+    const { isFullscreen, setIsFullscreen, isFull, toggleFull, setVisible } = useContext(DispatchHeader)!;
+    /******************************/
+
+    return <Col span={24}>
+        <Row gutter={[0, 10]} align='bottom'>
+            <Col {...(czChild ? { span: 24 } : { flex: '1 1 150px' })}>
+                {(isFullscreen && leftChild) && leftChild}
+            </Col>
+            {czChild && <Col flex='1 1 150px'>{czChild}</Col>}
+            <Col flex='0 1 150px'>
+                <Space style={{ float: 'right', marginBottom: 4 }}>
+                    <Button size='small' type='text' style={{ color: '#F56C6C' }} onClick={() => { setIsFullscreen(!isFullscreen) }}>
+                        <Tooltip title={isFullscreen ? '隐藏搜索' : '显示搜索'}><SearchOutlined /></Tooltip>
+                    </Button>
+                    {ajax && <Button size='small' type='text' style={{ color: '#67C23A' }} onClick={() => { ajax.refresh() }}>
+                        <Tooltip title='刷新'><RedoOutlined /></Tooltip>
+                    </Button>}
+                    <Button size='small' type='text' style={{ color: '#E6A23C' }} onClick={() => { setVisible(true) }}>
+                        <Tooltip title='设置'><SettingOutlined /></Tooltip>
+                    </Button>
+                    <Button type='text' size='small' style={{ color: '#409EFF' }} onClick={toggleFull}>
+                        {<Tooltip title={!isFull ? '全屏' : '退出全屏'}>{!isFull ? <FullscreenOutlined /> : <FullscreenExitOutlined />}</Tooltip>}
+                    </Button>
+                </Space>
+            </Col>
+        </Row>
+    </Col>
+}
+
+export default React.memo(Settings)

+ 158 - 0
src/components/TablePro/color.less

@@ -0,0 +1,158 @@
+.color0,
+.color0 > td {
+  background-color: #e1f5fe;
+  color: #000;
+}
+.color0,.color1,.color2,.color3,.color4,.color5,.color6,.color7,.color8,.color9,.color10,.color11,.color12,.color13,.color14,.color15,.color16,.color17,.color18,.color19,.color20,
+.color21,.color22,.color23,.color24,.color25,.color26,.color27,.color28,.color29,.color30 {
+  &:hover{
+    >td {
+      color: #fff;
+    }
+  }
+}
+.color1,
+.color1 > td {
+  background-color: #e8eaf6;
+  color: #000;
+}
+.color2,
+.color2 > td {
+  background-color: #f3e5f5;
+  color: #000;
+}
+.color3,
+.color3 > td {
+  background-color: #fff3e0;
+  color: #000;
+}
+.color4,
+.color4 > td {
+  background-color: #fce4ec;
+  color: #000;
+}
+.color5,
+.color5 > td {
+  background-color: #ffebee;
+  color: #000;
+}
+.color6,
+.color6 > td {
+  background-color: #fffde7;
+  color: #000;
+}
+.color7,
+.color7 > td {
+  background-color: #efebe9;
+  color: #000;
+}
+.color8,
+.color8 > td {
+  background-color: #f0f4c3;
+  color: #000;
+}
+.color9,
+.color9 > td {
+  background-color: #e8f5e9;
+  color: #000;
+}
+.color10,
+.color10 > td {
+  background-color: #e0f2f1;
+  color: #000;
+}
+.color11,
+.color11 > td {
+  background-color: #cfd8dc;
+  color: #000;
+}
+.color12,
+.color12 > td {
+  background-color: #d7ccc8;
+  color: #000;
+}
+.color13,
+.color13 > td {
+  background-color: #ffccbc;
+  color: #000;
+}
+.color14,
+.color14 > td {
+  background-color: #ffecb3;
+  color: #000;
+}
+.color15,
+.color15 > td {
+  background-color: #f0f4c3;
+  color: #000;
+}
+.color16,
+.color16 > td {
+  background-color: #dcedc8;
+  color: #000;
+}
+.color17,
+.color17 > td {
+  background-color: #c8e6c9;
+  color: #000;
+}
+.color18,
+.color18 > td {
+  background-color: #b2dfdb;
+  color: #000;
+}
+.color19,
+.color19 > td {
+  background-color: #b2ebf2;
+  color: #000;
+}
+.color21,
+.color21 > td {
+  background-color: #b3e5fc;
+  color: #000;
+}
+.color22,
+.color22 > td {
+  background-color: #bbdefb;
+  color: #000;
+}
+.color23,
+.color23 > td {
+  background-color: #c5cae9;
+  color: #000;
+}
+.color24,
+.color24 > td {
+  background-color: #d1c4e9;
+  color: #000;
+}
+.color25,
+.color25 > td {
+  background-color: #e1bee7;
+  color: #000;
+}
+.color26,
+.color26 > td {
+  background-color: #f8bbd0;
+  color: #000;
+}
+.color27,
+.color27 > td {
+  background-color: #ffcdd2;
+  color: #000;
+}
+.color28,
+.color28 > td {
+  background-color: #a5d6a7;
+  color: #000;
+}
+.color29,
+.color29 > td {
+  background-color: #ffcc80;
+  color: #000;
+}
+.color20,
+.color20 > td {
+  background-color: #80deea;
+  color: #000;
+}

+ 120 - 0
src/components/TablePro/index.less

@@ -0,0 +1,120 @@
+@import './color.less';
+
+.title {
+    text-align: center;
+    font-weight: bold;
+    padding: 4px 6px 6px;
+    font-size: 16px;
+    margin-bottom: 4px;
+    position: relative;
+}
+
+.cell {
+    text-align: center;
+    background-color: #fafafa;
+    color: rgb(212, 46, 46);
+}
+
+.middle {
+
+    tr,
+    td {
+        font-size: 14px !important;
+    }
+}
+
+.small {
+
+    tr,
+    td {
+        font-size: 13px !important;
+    }
+}
+
+.ranking {
+
+    tr,
+    td {
+        font-size: 14px !important;
+
+        span {
+            font-size: 14px;
+        }
+    }
+
+}
+
+.large {
+
+    tr,
+    td {
+        font-size: 14px !important;
+    }
+}
+
+.item_img {
+    width: 25px;
+    border-radius: 2px;
+    margin-right: 5px;
+    background-color: #efefef;
+}
+
+.blue {
+    background-color: cornflowerblue;
+}
+
+.cardtitle {
+    position: relative;
+    width: 100%;
+    height: 100%;
+
+    .text {
+        width: 100%;
+        height: 100%;
+        text-align: center;
+    }
+
+    .cardTab {
+        position: absolute;
+        width: 400px;
+        height: 100%;
+        top: 0;
+        left: 0;
+    }
+}
+
+
+.unfollow {
+    background-color: #fafafa;
+}
+
+.ant-table-row.expanded>.ant-table-cell {
+    padding: 5px 16px !important;
+}
+
+.ant-table-row.error {
+    background-color: #ffe9e9;
+}
+
+.ellipsisText {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: block;
+}
+
+.components-table-resizable-column .react-resizable {
+    position: relative;
+    background-clip: padding-box;
+}
+
+.components-table-resizable-column .react-resizable-handle {
+    position: absolute;
+    width: 10px;
+    height: 100%;
+    bottom: 0;
+    right: -9px;
+    cursor: col-resize;
+    z-index: 1;
+    border-left: white 1px solid;
+}

+ 253 - 0
src/components/TablePro/index.tsx

@@ -0,0 +1,253 @@
+import { QuestionCircleOutlined } from "@ant-design/icons";
+import { useDebounceFn, useFullscreen, useLocalStorageState, useMount, useSetState } from "ahooks";
+import { Card, Col, Row, Space, Tooltip } from "antd";
+import React, { useCallback, useEffect, useRef, useState } from "react"
+import style from './index.less'
+import Settings from "../Settings";
+import CustomListModel from "@/components/CustomList";
+import NewTable from "./newTable";
+
+const log = (text?: any, key?: string) => {
+    console.log(`pro_${key || ''}----->`, text)
+}
+export const DispatchContext = React.createContext<PROAPI.TableContentProps | null>(null);
+export const DispatchHeader = React.createContext<PROAPI.HeaderContentProps | null>(null);
+export const version = '1.0.0'
+const ran = Math.ceil(Math.random() * 100)
+/**
+ * 升级版表格
+ * @returns 
+ */
+const TablePro: React.FC<PROAPI.TableProProps> = ({
+    configName,
+    config,
+    fixed = { left: 0, right: 0 },
+    title,
+    tips,
+    ajax,
+    czChild,
+    leftChild,
+    size = 'small',
+    className,
+    total,
+    page,
+    pageSize,
+    scroll,
+    dataSource,
+    isZj,
+    totalData = [],
+    summary,
+    refreshAjax,
+    ...props
+}) => {
+
+    /*********************************************/
+    const [lsDataSource, setLsDataSource] = useLocalStorageState<PROAPI.ColumnsTypePro<any>>(`myAdMonitorConfig${version}_` + configName);
+    const [lsFixed] = useLocalStorageState<{ left: number, right: number }>(`myAdMonitorConfigFixed${version}_` + configName);
+    const [state, setState] = useSetState<PROAPI.State>({
+        // 所有配置用key转Object
+        configObj: {},
+        columns: [],
+        // 默认配置
+        defaultColumns: []
+    });
+    const ref = useRef(null)
+    const [isFull, { toggleFull }] = useFullscreen(ref);
+    const [isFullscreen, setIsFullscreen] = useState<boolean>(true);
+    const [visible, setVisible] = useState<boolean>(false)
+    // const { configObj, columns, defaultColumns } = state
+    /*********************************************/
+
+    // 首次渲染
+    useMount(() => {
+        log('----mount-----')
+        handleConfig()
+    });
+
+    /**
+     * 处理config成对象 减少后期轮询
+     * 拿到默认Columns
+     */
+    const handleConfig = () => {
+        // log(config)
+        let newColumns: PROAPI.ColumnsTypePro<any> = []
+        let newConfigObj: { [key: string]: PROAPI.ColumnTypePro<any>; } = {}
+        config?.forEach((item: { data: { default: any; dataIndex: string }[] }) => {
+            item?.data?.forEach((d: { default: any, dataIndex: string }) => {
+                newConfigObj[d.dataIndex] = { ...d }
+                if (d.default) {
+                    newColumns[d.default - 1] = d
+                }
+            })
+        })
+        setState({ defaultColumns: newColumns, configObj: newConfigObj })
+        handleColumns(newColumns, newConfigObj, lsDataSource, lsFixed)
+    }
+
+    // 处理columns
+    const handleColumns = (defaultColumns: PROAPI.ColumnsTypePro<any>, configObj: { [key: string]: PROAPI.ColumnTypePro<any>; }, lsDataSource?: PROAPI.ColumnsTypePro<any>, lsFixed?: { left: number, right: number }) => {
+        // log(defaultColumns, 'defaultColumns')
+        // log(configObj, 'configObj')
+        // log(lsDataSource, 'lsDataSource')
+
+        // 使用默认配置
+        let newColumns: PROAPI.ColumnsTypePro<any> = defaultColumns
+        let newFixed: { left: number, right: number } = fixed
+        if (lsFixed) {
+            newFixed = lsFixed
+        }
+        if (lsDataSource) { // 找到了设置的配置
+            newColumns = lsDataSource
+                .filter((item: { dataIndex: string | number; }) => !!item && configObj[item.dataIndex])
+                .map((item: { dataIndex: string | number; width: number }) => {
+                    let column = { ...configObj[item.dataIndex] }
+                    column['width'] = item.width
+                    return column
+                })
+        }
+        // log(newFixed, 'newFixed')
+        newColumns = newColumns.map(item => {
+            if (item?.tips && typeof item.title === 'string') {
+                let column: any = configObj[item.dataIndex]
+                item.title = <Space size={2}>
+                    {column.title}
+                    <Tooltip title={column?.tips} placement='bottom'>
+                        <QuestionCircleOutlined />
+                    </Tooltip>
+                </Space>
+            }
+            return { ...item, key: item.dataIndex }
+        })
+        if (newFixed.left || newFixed.right) {
+            for (let i = 0; i < Math.min(newFixed.left, newColumns.length); i++) {
+                newColumns[i] = { ...newColumns[i], fixed: 'left' };
+            }
+            const arrayLength = newColumns.length;
+            for (let i = Math.max(0, arrayLength - newFixed.right); i < arrayLength; i++) {
+                newColumns[i] = { ...newColumns[i], fixed: 'right' };
+            }
+        }
+        // log(newColumns, 'newColumns')
+        setState({ columns: newColumns })
+    }
+
+
+    useEffect(() => {
+        const contentBodyScroll = (e: any) => {
+            let el = document.querySelector(`.header_table_body_${ran} .ant-table-body`);
+            if (el) {
+                el.scrollLeft = e.target.scrollLeft
+            }
+        }
+        const headerBodyScroll = (e: any) => {
+            let el = document.querySelector(`.content_table_body_${ran} .ant-table-body`);
+            if (el) {
+                el.scrollLeft = e.target.scrollLeft
+            }
+        }
+        if (isZj) {
+            document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', contentBodyScroll);
+            document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.addEventListener('scroll', headerBodyScroll);
+        }
+        () => {
+            if (isZj) {
+                document.querySelector(`.content_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', contentBodyScroll);
+                document.querySelector(`.header_table_body_${ran} .ant-table-body`)?.removeEventListener('scroll', headerBodyScroll);
+            }
+        }
+    }, [isZj, ran])
+
+    const { run: runResize } = useDebounceFn((columns) => {
+        if (configName) {
+            let newSelectData: PROAPI.ColumnsTypePro<any> = []
+            state.columns?.forEach((item, index) => {
+                newSelectData.push({ ...JSON.parse(JSON.stringify(state.configObj[item.dataIndex])), width: columns[index]['width'] })
+            })
+            setLsDataSource(newSelectData)
+            if (isZj) { // 有总计需要刷新内容表格
+                handleColumns(state.defaultColumns, state.configObj, newSelectData, lsFixed)
+            }
+        }
+    }, { wait: 200 });
+
+    //拖动宽度设置设置保存
+    const handelResize = useCallback((columns: PROAPI.ColumnTypePro<any>) => {
+        runResize(columns)
+    }, [configName, state.columns])
+
+    return <>
+        {/* 设置展示参数 */}
+        {visible && <CustomListModel
+            columns={state.columns}
+            sysFixed={fixed}
+            version={version}
+            config={config}
+            configName={configName}
+            visible={visible}
+            onClose={() => { setVisible(false) }}
+            onChange={(value) => {
+                refreshAjax?.()
+                if (value) {
+                    handleColumns(state.defaultColumns, state.configObj, value?.selectData, value?.fixed)
+                } else {
+                    handleColumns(state.defaultColumns, state.configObj)
+                }
+            }}
+        />}
+        <Row gutter={[0, 20]} ref={ref} style={isFull ? { background: '#fff' } : {}}>
+            <Col span={24}>
+                <Card
+                    style={{ borderRadius: 8 }}
+                    headStyle={{ textAlign: 'left' }}
+                    bodyStyle={{ padding: '5px 10px' }}
+                >
+                    {title && <div className={style.title}>
+                        <Space><span>{title}</span>{tips && <Tooltip title={tips}><span><QuestionCircleOutlined /></span></Tooltip>}</Space>
+                    </div>}
+                    <Row gutter={[0, 10]}>
+                        <DispatchHeader.Provider value={{ setIsFullscreen, isFullscreen, isFull, toggleFull, setVisible }}>
+                            <Settings
+                                leftChild={leftChild}
+                                czChild={czChild}
+                                ajax={ajax}
+                            />
+                        </DispatchHeader.Provider>
+                        <DispatchContext.Provider value={{ total, page, pageSize, handelResize }}>
+                            <Col span={24}>
+                                <div className={`${style[size]} ${className ? style[className] : ''} `}>
+                                    {
+                                        isZj && <NewTable
+                                            bordered
+                                            columns={state.columns}
+                                            dataSource={totalData?.length > 0 ? [...totalData] : [{ id: 1 }]}
+                                            scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
+                                            size={size}
+                                            pagination={false}
+                                            className={`all_table header_table_body header_table_body_${ran} ${className ? className : ''}`}
+                                            sortDirections={['ascend', 'descend', null]}
+                                            {...props}
+                                        />
+                                    }
+                                    <NewTable
+                                        showHeader={!isZj}
+                                        sortDirections={['ascend', 'descend', null]}
+                                        bordered
+                                        className={`all_table content_table_body content_table_body_${ran} ${className ? className : ''}`}
+                                        scroll={scroll ? isFull ? { ...scroll, y: document.body.clientHeight - 300 } : scroll : {}}
+                                        columns={state.columns}
+                                        dataSource={dataSource}
+                                        summary={summary}
+                                        pagination={{}}
+                                        {...props}
+                                    />
+                                </div>
+                            </Col>
+                        </DispatchContext.Provider>
+                    </Row>
+                </Card>
+            </Col>
+        </Row>
+    </>
+}
+
+export default React.memo(TablePro)

+ 77 - 0
src/components/TablePro/newTable.tsx

@@ -0,0 +1,77 @@
+import { Table, TableProps, Tag } from "antd"
+import React, { useContext, useEffect, useRef, useState } from "react"
+import { DispatchContext } from ".";
+import { Resizable } from "react-resizable";
+import './index.less'
+
+const ResizableHeader = (props: { [x: string]: any; onResize: any; width: any }) => {
+    const { onResize, width, ...restProps } = props;
+    if (!width) {
+        return <th {...restProps} />
+    }
+    return <Resizable width={width} height={0} onResize={onResize} draggableOpts={{ enableUserSelectHack: false }}>
+        <th {...restProps} />
+    </Resizable>
+}
+
+const NewTable: React.FC<TableProps<any>> = ({ columns, pagination, ...props }) => {
+
+    /************************************/
+    const { total, page: current, pageSize, onPaginationChange, handelResize } = useContext(DispatchContext)!;
+    let ww = document.body.clientWidth < 415
+    const colsRef = useRef<any[]>([])
+    const [cols, setCols] = useState<any>(columns)
+    /************************************/
+
+    const components = {
+        header: {
+            cell: ResizableHeader
+        }
+    }
+
+    useEffect(() => {
+        setCols(columns)
+    }, [columns])
+
+    const handleResize = (index: any) => (e: any, { size }: any) => {
+        const nextColumns = [...cols]
+        nextColumns[index] = {
+            ...nextColumns[index],
+            width: size.width
+        }
+        setCols(nextColumns)
+        handelResize && handelResize(nextColumns)
+    }
+    colsRef.current = (cols || []).map((col: any, index: any) => ({
+        ...col,
+        onHeaderCell: (column: any) => ({
+            width: column.width,
+            onResize: handleResize(index)
+        })
+    }))
+
+    return <div className='components-table-resizable-column'>
+        <Table
+            rowKey={(a: any) => (JSON.stringify(a?.id) || JSON.stringify(a?.key))}
+            pagination={pagination ? {
+                size: 'small',
+                total,//总共多少条数据,服务器给,设置后分页自动计算页数
+                current,//当前页数,需state控制配合翻页
+                pageSize,
+                defaultCurrent: 1,//默认初始的当前页数
+                defaultPageSize: 20,//默认初始的每页条数
+                pageSizeOptions: ['10', '20', '30', '40', '50', '60', '70', '80', '90', '100'],
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                showSizeChanger: true, //手动开启条数筛选器,默认超过50条开启
+                onChange: onPaginationChange, //点击页码或条数筛选时触发获取当前页数和每页条数
+                simple: ww ? true : false,//开启简单分页
+                showLessItems: true
+            } : false}
+            components={components}
+            columns={colsRef?.current}
+            {...props}
+        />
+    </div>
+}
+
+export default React.memo(NewTable)

+ 52 - 0
src/components/TablePro/typings.d.ts

@@ -0,0 +1,52 @@
+declare namespace PROAPI {
+    type ColumnTypePro<RecordType = unknown> = (ColumnGroupType<RecordType> | ColumnType<RecordType>) & { label: string };
+    type ColumnsTypePro<RecordType = unknown> = ColumnTypePro[];
+    type TableProProps<RecordType = unknown> = TableProps<RecordType> & {
+        configName: string;
+        config: any;
+        fixed?: {
+            left: number,
+            right: number
+        };
+        title?: string;
+        tips?: JSX.Element; // 提示
+        czChild?: JSX.Element;
+        leftChild?: JSX.Element;
+        ajax?: any;//接口刷新
+        size?: 'small' | 'middle' | 'large',
+        className?: string,//自定义class
+        total?: number;
+        page?: number,
+        pageSize?: number,
+        scroll?: ({
+            x?: string | number | true | undefined;
+            y?: string | number | undefined;
+        } & {
+            scrollToFirstRowOnChange?: boolean | undefined;
+        }),
+        dataSource?: readonly any[]
+        isZj?: boolean,//是否查总计
+        totalData?: any[]
+        refreshAjax?: () => void
+    };
+    type State = {
+        columns: any[];
+        configObj: { [key: string]: ColumnTypePro<any>; };
+        defaultColumns: any[];
+    };
+    type HeaderContentProps = {
+        isFullscreen: boolean;
+        setIsFullscreen: React.Dispatch<React.SetStateAction<boolean>>;
+        isFull: boolean;
+        toggleFull: () => void;
+        setVisible: React.Dispatch<React.SetStateAction<boolean>>;
+        
+    };
+    type TableContentProps = {
+        total?: number;
+        page?: number;
+        pageSize?: number;
+        onPaginationChange?: (page: number, pageSize?: number) => void,
+        handelResize?: (columns: any) => void
+    }
+}

+ 4 - 0
src/global.less

@@ -315,4 +315,8 @@ body {
 .tableScrollbar::-webkit-scrollbar {
   width: 0;
   height: 0;
+}
+
+.all_table .ant-table-cell {
+  padding: 2px 5px !important;
 }

+ 179 - 0
src/pages/launchSystemV3/components/PositionPackage/index.tsx

@@ -0,0 +1,179 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, message, Modal, Radio, Space, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from './tableConfig'
+import { getBatchUnionPositionPackagesApi, getWechatOfficialAccountApi, getWechatOfficialAccountsApi } from "@/services/adqV3/global"
+
+/**
+ * 优量汇流量包
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const PositionPackage: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange } = props
+    const [tableData, setTableData] = useState<any[]>([])//table数据
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+    const [loading, setLoading] = useState<boolean>(false)
+    const [positionPackageType, setPositionPackageType] = useState<'unionPositionPackage' | 'excludeUnionPositionPackage'>('unionPositionPackage')
+
+    const getBatchUnionPositionPackages = useAjax((params) => getBatchUnionPositionPackagesApi(params))
+    const getBatchUnionPositionPackagesS = useAjax((params) => getBatchUnionPositionPackagesApi(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList(data[selectAdz - 1].accountId, positionPackageType)
+        } else {
+            setTableData([])
+        }
+    }, [selectAdz, positionPackageType])
+
+    // 获取流量包列表
+    const getList = (accountId: number, positionPackageType: 'unionPositionPackage' | 'excludeUnionPositionPackage') => {
+        getBatchUnionPositionPackages.run({ accountIds: [accountId], unionPackageType: positionPackageType === 'unionPositionPackage' ? 'UNION_PACKAGE_TYPE_INCLUDE' : 'UNION_PACKAGE_TYPE_EXCLUDE' }).then(res => {
+            if (res?.length) {
+                let data = res.find((item: { accountId: number }) => item.accountId === accountId)?.result || []
+                setTableData(data)
+            } else {
+                setTableData([])
+            }
+        })
+    }
+
+    const handleOk = () => {
+        onChange?.(data)
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (_: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1][positionPackageType] = selectedRows?.length > 5 ? selectedRows.slice(0, 5) : selectedRows
+        setData([...newData])
+    }
+
+    // 清空已选
+    const clearGoods = () => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1][positionPackageType] = []
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        let unionPackageNames: string[] = data[selectAdz - 1][positionPackageType]?.map((item: { unionPackageName: string }) => item.unionPackageName) || []
+            getBatchUnionPositionPackagesS.run({ accountIds: newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => item?.accountId), unionPackageType: positionPackageType === 'unionPositionPackage' ? 'UNION_PACKAGE_TYPE_INCLUDE' : 'UNION_PACKAGE_TYPE_EXCLUDE' }).then(res => {
+                if (res?.length > 0) {
+                    res.forEach((i: { accountId: number, result: any[] }) => {
+                        newData = newData.map(item => {
+                            let searchData =i?.result?.filter(item => unionPackageNames.includes(item.unionPackageName))
+                            if (item.accountId.toString() === i.accountId.toString() && searchData.length) {
+                                return { ...item, [positionPackageType]: searchData }
+                            }
+                            return item
+                        })
+                    })
+                    setData(newData)
+                }
+                message.success('设置完成');
+                hide()
+            })
+    }
+
+
+    return <Modal
+        title={<strong>选择优量汇流量包</strong>}
+        open={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={900}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        let positionPackage = data[index]?.[positionPackageType] || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {positionPackage?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="center">
+                    <Radio.Group value={positionPackageType} onChange={(e) => setPositionPackageType(e.target.value)} buttonStyle="solid">
+                        <Radio.Button value="unionPositionPackage">定投流量包</Radio.Button>
+                        <Radio.Button value="excludeUnionPositionPackage">屏蔽流量包</Radio.Button>
+                    </Radio.Group>
+                    <Button icon={<SyncOutlined />} type='link' style={{ padding: 0 }} loading={getBatchUnionPositionPackages?.loading || getBatchUnionPositionPackagesS.loading} onClick={() => { getList(data[selectAdz - 1].accountId, positionPackageType) }}>刷新</Button>
+                    {data?.length > 1 && <Button style={{ padding: 0 }} disabled={!data[selectAdz - 1][positionPackageType]?.length} onClick={() => setOnekey()} type="link" loading={getBatchUnionPositionPackages.loading || getBatchUnionPositionPackagesS.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同名称的商品为那个账号的商品(注意需要用户商品称相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.[positionPackageType] || [])?.length > 0 && <Button style={{ padding: 0 }} type='link' danger onClick={() => { clearGoods() }}>清空{positionPackageType === 'unionPositionPackage' ? '定投流量包' : '屏蔽流量包'}</Button>}
+                </Space>
+                <Table
+                    columns={columns()}
+                    dataSource={tableData}
+                    size="small"
+                    loading={getBatchUnionPositionPackages?.loading}
+                    scroll={{ y: 400 }}
+                    rowKey={'unionPackageId'}
+                    rowSelection={{
+                        selectedRowKeys: data[selectAdz - 1]?.[positionPackageType]?.map((item: any) => item?.unionPackageId),
+                        getCheckboxProps: (record: any) => {
+                            let selectData = data?.[selectAdz - 1]?.[positionPackageType];
+                            return {
+                                disabled: selectData && selectData?.length >= 5 ? selectData.map(item => item.unionPackageId).includes(record.unionPackageId) ? false : true : false
+                            }
+                        },
+                        onChange: onChangeTable
+                    }}
+                    onRow={(record) => ({
+                        onClick: () => {
+                            let newDatas = JSON.parse(JSON.stringify(data))
+                            let oldData = newDatas[selectAdz - 1]?.[positionPackageType] || []
+                            const selected = oldData?.some((item: any) => item?.unionPackageId === record.unionPackageId);
+                            const newSelectedRows = selected
+                                ? oldData?.filter((item: any) => item?.unionPackageId !== record.unionPackageId)
+                                : [...oldData, record];
+                            newDatas[selectAdz - 1][positionPackageType] = newSelectedRows;
+                            setData([...newDatas])
+                        },
+                    })}
+                />
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(PositionPackage)

+ 38 - 0
src/pages/launchSystemV3/components/PositionPackage/tableConfig.tsx

@@ -0,0 +1,38 @@
+import { TableProps } from "antd"
+import React from "react"
+import moment from "moment"
+
+const columns = (): TableProps<any>['columns'] => [
+    {
+        title: '流量包名称',
+        dataIndex: 'unionPackageName',
+        key: 'unionPackageName',
+        ellipsis: true,
+        width: 180,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: '流量包ID',
+        dataIndex: 'unionPackageId',
+        key: 'unionPackageId',
+        ellipsis: true,
+        width: 120,
+        align: 'center',
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: '最后更新时间',
+        dataIndex: 'lastModifiedTime',
+        key: 'lastModifiedTime',
+        ellipsis: true,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{moment(value * 1000).format('YYYY-MM-DD')}</span>
+        }
+    },
+]
+
+export default columns

+ 43 - 2
src/pages/launchSystemV3/material/tencent/const.ts

@@ -17,7 +17,7 @@ export const showFieldList = [
     { label: '下单金额', value: 'material_data.order_amount', field: 'order_amount' },
     { label: '下单次数(点击归因)', value: 'material_data.order_by_click_count', field: 'order_by_click_count' },
     { label: '下单金额(点击归因)', value: 'material_data.order_by_click_amount', field: 'order_by_click_amount' },
-    { label: '用户点击广告当天内,完成的下单次数加和', value: 'material_data.first_day_order_by_click_count', field: 'first_day_order_by_click_count' },
+    { label: '点击广告当天内,下单次数', value: 'material_data.first_day_order_by_click_count', field: 'first_day_order_by_click_count' },
     // { label: '点击首日付费次数', value: 'material_data.cheout_pv1d', field: 'cheout_pv1d' },
     // { label: '点击首日付费金额', value: 'material_data.cheout_fd', field: 'cheout_fd' },
     // { label: '点击首日付费成本', value: 'material_data.cheout1d_cost', field: 'cheout1d_cost' },
@@ -42,4 +42,45 @@ export const showFieldList = [
     { label: '下单率(点击归因)', value: 'calculate_material_data.order_by_click_rate', field: 'order_by_click_rate', isRate: true },
     { label: '下单成本(点击归因)', value: 'calculate_material_data.order_by_click_cost', field: 'order_by_click_cost' },
     { label: '首日新增下单ROI', value: 'calculate_material_data.first_day_order_roi', field: 'first_day_order_roi', isRate: true },
-]
+]
+
+export const defaultFiledList = ['created_time', 'cost', 'view_count', 'valid_click_count', 'from_follow_by_click_uv', 'from_follow_by_click_cost', 'biz_follow_uv', 'conversions_count', 'conversions_cost', 'deep_conversions_count', 'scan_follow_count', 'scan_follow_user_count', 'order_pv', 'order_amount', 'order_by_click_count', 'order_by_click_amount', 'first_day_order_by_click_count', 'first_day_order_count', 'first_day_order_by_click_amount', 'first_day_order_amount', 'cpc', 'thousand_display_price', 'ctr', 'biz_follow_cost', 'biz_follow_rate', 'conversions_rate', 'deep_conversions_rate', 'scan_follow_user_cost', 'scan_follow_user_rate', 'order_unit_price', 'order_rate', 'order_by_display_cost', 'order_roi', 'order_by_click_rate', 'order_by_click_cost', 'first_day_order_roi']
+
+export const fieldData = {
+    "created_time": "created_time",
+    "cost": "material_data.cost",
+    "view_count": "material_data.view_count",
+    "valid_click_count": "material_data.valid_click_count",
+    "from_follow_by_click_uv": "material_data.from_follow_by_click_uv",
+    "from_follow_by_click_cost": "material_data.from_follow_by_click_cost",
+    "biz_follow_uv": "material_data.biz_follow_uv",
+    "conversions_count": "material_data.conversions_count",
+    "conversions_cost": "material_data.conversions_cost",
+    "deep_conversions_count": "material_data.deep_conversions_count",
+    "scan_follow_count": "material_data.scan_follow_count",
+    "scan_follow_user_count": "material_data.scan_follow_user_count",
+    "order_pv": "material_data.order_pv",
+    "order_amount": "material_data.order_amount",
+    "order_by_click_count": "material_data.order_by_click_count",
+    "order_by_click_amount": "material_data.order_by_click_amount",
+    "first_day_order_by_click_count": "material_data.first_day_order_by_click_count",
+    "first_day_order_count": "material_data.first_day_order_count",
+    "first_day_order_by_click_amount": "material_data.first_day_order_by_click_amount",
+    "first_day_order_amount": "material_data.first_day_order_amount",
+    "cpc": "calculate_material_data.cpc",
+    "thousand_display_price": "calculate_material_data.thousand_display_price",
+    "ctr": "calculate_material_data.ctr",
+    "biz_follow_cost": "calculate_material_data.biz_follow_cost",
+    "biz_follow_rate": "calculate_material_data.biz_follow_rate",
+    "conversions_rate": "calculate_material_data.conversions_rate",
+    "deep_conversions_rate": "calculate_material_data.deep_conversions_rate",
+    "scan_follow_user_cost": "calculate_material_data.scan_follow_user_cost",
+    "scan_follow_user_rate": "calculate_material_data.scan_follow_user_rate",
+    "order_unit_price": "calculate_material_data.order_unit_price",
+    "order_rate": "calculate_material_data.order_rate",
+    "order_by_display_cost": "calculate_material_data.order_by_display_cost",
+    "order_roi": "calculate_material_data.order_roi",
+    "order_by_click_rate": "calculate_material_data.order_by_click_rate",
+    "order_by_click_cost": "calculate_material_data.order_by_click_cost",
+    "first_day_order_roi": "calculate_material_data.first_day_order_roi"
+}

+ 49 - 0
src/pages/launchSystemV3/material/tencent/index.less

@@ -0,0 +1,49 @@
+.preview_table {
+    display: flex;
+    gap: 10px;
+
+    .preview_content {
+        width: 100px;
+        height: 60px;
+        background-color: #ececec;
+        border-radius: 8px;
+        position: relative;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        transition: all 0.2s;
+        overflow: hidden;
+
+        .coverImg {
+            max-width: 100%;
+            max-height: 100%;
+        }
+
+        .playr {
+            position: absolute;
+            width: 35px;
+            height: 35px;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+
+            >img {
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+
+    .body {
+        display: flex;
+        flex-direction: column;
+        gap: 1px;
+        width: calc(100% - 110px);
+
+        >div {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+    }
+}

+ 72 - 8
src/pages/launchSystemV3/material/tencent/index.tsx

@@ -1,17 +1,19 @@
 import { useAjax } from "@/Hook/useAjax"
-import { getRemoteMaterialListApi } from "@/services/adqV3/cloudNew"
+import { getRemoteMaterialListApi, getRemoteMaterialTotalApi } from "@/services/adqV3/cloudNew"
 import React, { useEffect, useRef, useState } from "react"
 import style from '../cloudNew/index.less'
 import Search from "./search"
-import { Button, Card, Checkbox, Divider, Dropdown, Empty, Form, message, Pagination, Popover, Radio, Select, Space, Spin, Statistic, Typography, Image, Tag } from "antd"
+import { Button, Card, Checkbox, Divider, Dropdown, Empty, Form, message, Pagination, Popover, Radio, Select, Space, Spin, Statistic, Typography, Image, Tag, Table } from "antd"
 import { EyeOutlined, SortAscendingOutlined } from "@ant-design/icons"
 import PlayVideo from "../cloudNew/playVideo"
 import { formatBytes, formatSecondsToTime } from "@/utils/utils"
 import { useLocalStorageState, useSize } from "ahooks"
-import { showFieldList } from "./const"
+import { defaultFiledList, fieldData, showFieldList } from "./const"
 import Lazyimg from "react-lazyimg-component"
 import '../cloudNew/global.less'
 import SaveMaterial from "./saveMaterial"
+import columns12 from "./tableConfig"
+import TablePro from "@/components/TablePro"
 const { Text, Paragraph } = Typography;
 
 const Tencent: React.FC = () => {
@@ -19,6 +21,8 @@ const Tencent: React.FC = () => {
     /*******************************/
     const ref = useRef<HTMLDivElement>(null);
     const size = useSize(ref);
+    const ref1 = useRef<HTMLDivElement>(null);
+    const size1 = useSize(ref1);
     const [rowNum, setRowNum] = useState<number>(0)
     const [queryParams, setQueryParams] = useState<CLOUDNEW.GetRemoteMaterialListProps>({ pageNum: 1, pageSize: 50 })
     const [searchParams, setSearchParams] = useState<Partial<CLOUDNEW.GetRemoteMaterialListProps>>({})
@@ -29,17 +33,42 @@ const Tencent: React.FC = () => {
     const [sortData, setSortData] = useLocalStorageState<{ sortField: string | undefined, sortType: boolean }>('sort-tencent-data', { sortField: undefined, sortType: false });
     const [previewData, setPreviewData] = useState<{ visible: boolean, url?: string }>({ visible: false })
     const [moveData, setMoveData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: [] })
+    const [pageShowType, setPageShowType] = useLocalStorageState<'List' | 'Kanban'>('PAGE_SHOW_TYPE', 'Kanban');
+    const [totalData, setTotalData] = useState<any[]>([{ id: 1, preview: '总计' }])
 
     const getRemoteMaterialList = useAjax((params) => getRemoteMaterialListApi(params))
+    const getRemoteMaterialTotal = useAjax((params) => getRemoteMaterialTotalApi(params))
     /*******************************/
 
     useEffect(() => {
+        getList()
+    }, [queryParams, searchParams, sortData, showField, pageShowType])
+
+    const getList = () => {
         let params = { ...queryParams, ...searchParams, columns: showField || [] }
         if (sortData?.sortField) {
             params = { ...params, ...sortData }
         }
+        if (pageShowType === 'List') {
+            let saveColumns = localStorage.getItem('myAdMonitorConfig1.0.0_云端素材')
+            let fieldList = defaultFiledList
+            if (saveColumns) {
+                fieldList = JSON.parse(saveColumns)?.map((item: { dataIndex: any }) => item.dataIndex).filter((key: string) => !['preview', 'pitcher_name'].includes(key))
+            }
+            params.columns = fieldList.map(key => fieldData[key as keyof typeof fieldData])
+        }
         getRemoteMaterialList.run(params)
-    }, [queryParams, searchParams, sortData, showField])
+        getRemoteMaterialTotal.run(params).then((res: { data: { id: number; preview: string } }) => {
+            if (res) {
+                let data: any = res
+                data.id = 1
+                data.preview = '总计'
+                setTotalData([data])
+            } else {
+                setTotalData([{ id: 1, preview: '总计' }])
+            }
+        })
+    }
 
     // 根据内容宽度计算列数
     useEffect(() => {
@@ -80,12 +109,14 @@ const Tencent: React.FC = () => {
         setCheckedFolderList(newCheckedFolderList)
     };
 
-    return <div className={style.cloudNew_layout}>
+    return <div className={style.cloudNew_layout} ref={ref1}>
         {/* 搜索 */}
         <Search
+            pageShowType={pageShowType}
+            setPageShowType={setPageShowType}
             onSearch={(value) => { setSearchParams(value) }}
         />
-        <Card
+        {pageShowType === 'Kanban' ? <Card
             style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
             bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
             className="cardResetCss buttonResetCss"
@@ -230,6 +261,10 @@ const Tencent: React.FC = () => {
                                                     </div>
                                                     <Divider style={{ margin: '0 0 4px 0' }} />
                                                     <div style={{ padding: '0 10px 6px' }}>
+                                                        <div style={{ display: 'flex', marginBottom: 1 }}>
+                                                            <div style={{ maxWidth: 120, display: 'flex' }}><Paragraph ellipsis={{ tooltip: { mouseEnterDelay: 0.5, placement: 'bottom' } }} style={{ fontSize: 12, marginBottom: 0 }}>设计师</Paragraph>:</div>
+                                                            <div style={{ flex: '1 0' }}>{item?.create_name || '--'}</div>
+                                                        </div>
                                                         {showFieldList.filter(f => showField.includes(f.value)).map(f => {
                                                             return <div key={f.value} style={{ display: 'flex', marginBottom: 1 }}>
                                                                 <div style={{ maxWidth: 120, display: 'flex' }}><Paragraph ellipsis={{ tooltip: { mouseEnterDelay: 0.5, placement: 'bottom' } }} style={{ fontSize: 12, marginBottom: 0 }}>{f.label}</Paragraph>:</div>
@@ -240,7 +275,7 @@ const Tencent: React.FC = () => {
                                                     <Divider style={{ margin: '0 0 4px 0' }} />
                                                     <div className={style.actions}>
                                                         <div style={{ height: 22, fontSize: 12 }}>
-                                                            {item.pitcher_name}
+                                                            投手:{item.pitcher_name}
                                                         </div>
                                                         <Dropdown menu={{
                                                             items: [{ label: '保存至素材库', key: '1', onClick: () => { setMoveData({ visible: true, data: [item] }) } }]
@@ -272,7 +307,36 @@ const Tencent: React.FC = () => {
                     </div>
                 </div>
             </div>
-        </Card>
+        </Card> : <TablePro
+            leftChild={<div></div>}
+            refreshAjax={getList}
+            isZj
+            totalData={totalData}
+            config={columns12()}
+            fixed={{ left: 1, right: 0 }}
+            scroll={{ x: 1000, y: size1.height ? size1.height - 270 : 580 }}
+            configName='云端素材'
+            loading={getRemoteMaterialList.loading}
+            ajax={getRemoteMaterialList}
+            page={getRemoteMaterialList?.data?.current || 1}
+            pageSize={getRemoteMaterialList?.data?.size || 20}
+            total={getRemoteMaterialList?.data?.total || 0}
+            dataSource={getRemoteMaterialList?.data?.records?.map((item: any, index: number) => ({ ...item, id: Number(queryParams.pageNum.toString() + (index + '')) }))}
+            onChange={(pagination: any, _: any, sortData: any) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryParams))
+                if (sortData && sortData?.order) {
+                    newQueryForm['sortType'] = sortData?.order === 'ascend' ? true : false
+                    newQueryForm['sortField'] = sortData?.field
+                } else {
+                    delete newQueryForm['sortType']
+                    delete newQueryForm['sortField']
+                }
+                newQueryForm.pageNum = current || newQueryForm.pageNum
+                newQueryForm.pageSize = pageSize || newQueryForm.pageSize
+                setQueryParams({ ...newQueryForm })
+            }}
+        />}
 
         {/* 预览 */}
         {previewData.visible && <Image

+ 89 - 56
src/pages/launchSystemV3/material/tencent/search.tsx

@@ -1,12 +1,15 @@
-import { Button, Card, Col, Form, Input, InputNumber, Row, Select, Space } from "antd"
+import { Button, Card, Col, Form, InputNumber, Row, Segmented, Select, Space } from "antd"
 import React, { useEffect } from "react"
 import { getUserAllApi } from "@/services/operating/account";
 import { useAjax } from "@/Hook/useAjax";
-import { SearchOutlined } from "@ant-design/icons";
+import { AppstoreOutlined, BarsOutlined, SearchOutlined } from "@ant-design/icons";
 import moment from "moment";
 import '../../tencentAdPutIn/index.less'
+import { IFuncUpdater } from "ahooks/lib/createUseStorageState";
 
 interface Props {
+    pageShowType: "List" | "Kanban"
+    setPageShowType: (value?: "List" | "Kanban" | IFuncUpdater<"List" | "Kanban"> | undefined) => void
     onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
 }
 
@@ -15,7 +18,7 @@ interface Props {
  * @param param0 
  * @returns 
  */
-const Search: React.FC<Props> = ({ onSearch }) => {
+const Search: React.FC<Props> = ({ onSearch, pageShowType, setPageShowType }) => {
 
     /**********************************/
     const [form] = Form.useForm();
@@ -52,58 +55,73 @@ const Search: React.FC<Props> = ({ onSearch }) => {
         className="cardResetCss buttonResetCss"
         bordered
     >
-        <Form
-            layout="inline"
-            name="basicSelectSearch"
-            colon={false}
-            form={form}
-            onFinish={handleOk}
-            initialValues={{
-                sizeQueries: {
-                    relation: '='
-                }
-            }}
-        >
-            <Row gutter={[0, 6]}>
-                <Col><Form.Item name="materialType">
-                    <Select
-                        placeholder="素材类型"
-                        filterOption={(input, option) =>
-                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
-                        }
-                        allowClear
-                        style={{ width: 120 }}
-                        options={[
-                            { label: '图片', value: 'image' },
-                            { label: '视频', value: 'video' }
-                        ]}
-                    />
-                </Form.Item></Col>
-                <Col><Form.Item name={['sizeQueries', 'width']}>
-                    <InputNumber placeholder="素材宽" />
-                </Form.Item></Col>
-                <Col><Form.Item name={['sizeQueries', 'height']}>
-                    <InputNumber placeholder="素材高" />
-                </Form.Item></Col>
-                <Col>
-                    <Form.Item name={['sizeQueries', 'relation']}>
+        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
+            <Form
+                layout="inline"
+                name="basicSelectSearch"
+                colon={false}
+                form={form}
+                onFinish={handleOk}
+                initialValues={{
+                    sizeQueries: {
+                        relation: '='
+                    }
+                }}
+            >
+                <Row gutter={[0, 6]}>
+                    <Form.Item name={'designerIds'}>
                         <Select
-                            placeholder="运算符"
+                            placeholder="设计师"
                             filterOption={(input, option) =>
                                 ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
                             }
-                            style={{ width: 100 }}
+                            style={{ minWidth: 120 }}
+                            maxTagCount={1}
+                            mode="multiple"
+                            allowClear
+                            loading={getUserAll.loading}
+                            options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                        />
+                    </Form.Item>
+                    <Col><Form.Item name="materialType">
+                        <Select
+                            placeholder="素材类型"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            style={{ width: 120 }}
                             options={[
-                                { label: '小于', value: '<' },
-                                { label: '小于等于', value: '<=' },
-                                { label: '等于', value: '=' },
-                                { label: '大于', value: '>' },
-                                { label: '大于等于', value: '>=' },
+                                { label: '图片', value: 'image' },
+                                { label: '视频', value: 'video' }
                             ]}
                         />
-                    </Form.Item>
-                </Col>
-                {/* <Col><Form.Item name={'sysUserIds'}>
+                    </Form.Item></Col>
+                    <Col><Form.Item name={['sizeQueries', 'width']}>
+                        <InputNumber placeholder="素材宽" />
+                    </Form.Item></Col>
+                    <Col><Form.Item name={['sizeQueries', 'height']}>
+                        <InputNumber placeholder="素材高" />
+                    </Form.Item></Col>
+                    <Col>
+                        <Form.Item name={['sizeQueries', 'relation']}>
+                            <Select
+                                placeholder="运算符"
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                style={{ width: 100 }}
+                                options={[
+                                    { label: '小于', value: '<' },
+                                    { label: '小于等于', value: '<=' },
+                                    { label: '等于', value: '=' },
+                                    { label: '大于', value: '>' },
+                                    { label: '大于等于', value: '>=' },
+                                ]}
+                            />
+                        </Form.Item>
+                    </Col>
+                    {/* <Col><Form.Item name={'sysUserIds'}>
                     <Select
                         placeholder="投手"
                         filterOption={(input, option) =>
@@ -116,14 +134,29 @@ const Search: React.FC<Props> = ({ onSearch }) => {
                         options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
                     />
                 </Form.Item></Col> */}
-                <Col><Form.Item>
-                    <Space>
-                        <Button onClick={() => form.resetFields()}>重置</Button>
-                        <Button type="primary" htmlType="submit" icon={<SearchOutlined />} className="modalResetCss">搜索</Button>
-                    </Space>
-                </Form.Item></Col>
-            </Row>
-        </Form>
+                    <Col><Form.Item>
+                        <Space>
+                            <Button onClick={() => form.resetFields()}>重置</Button>
+                            <Button type="primary" htmlType="submit" icon={<SearchOutlined />} className="modalResetCss">搜索</Button>
+                        </Space>
+                    </Form.Item></Col>
+                </Row>
+            </Form>
+            <Segmented
+                value={pageShowType}
+                onChange={(value: any) => setPageShowType(value)}
+                options={[
+                    {
+                        value: 'Kanban',
+                        icon: <AppstoreOutlined />
+                    },
+                    {
+                        value: 'List',
+                        icon: <BarsOutlined />
+                    }
+                ]}
+            />
+        </div>
     </Card>
 }
 

+ 198 - 0
src/pages/launchSystemV3/material/tencent/tableConfig.tsx

@@ -0,0 +1,198 @@
+import React from "react"
+import Lazyimg from "react-lazyimg-component"
+import style from './index.less'
+import { Statistic, Typography } from "antd";
+import { formatBytes, formatSecondsToTime } from "@/utils/utils";
+import PlayVideo from "../cloudNew/playVideo";
+const { Text } = Typography;
+
+function columns12(): { label: string, fieldSHow?: { label: string, saveField: string, defaultValue: any[], data: any[] }, data: any[] }[] {
+
+    return [
+        {
+            label: '自定义指标',
+            data: [
+                {
+                    title: '素材预览', dataIndex: 'preview', label: '自定义指标', width: 320, default: 1,
+                    render: (a: any, records: any) => {
+                        if (a === '总计')
+                            return <Text ellipsis strong>总计</Text>
+                        return <div className={style.preview_table}>
+                            <div className={style.preview_content}>
+                                {records.source === 'video' && <div className={style.playr}>
+                                    <PlayVideo videoUrl={records.preview_url}>{(onPlay) => <img onClick={(e) => {
+                                        e.stopPropagation(); e.preventDefault()
+                                        onPlay()
+                                    }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
+                                </div>}
+                                <Lazyimg
+                                    animateType="transition"
+                                    src={records.source === 'image' ? records.preview_url : records?.key_frame_image_url}
+                                    className={`${style.coverImg} lazy`}
+                                    animateClassName={['transition-enter', 'transition-enter-active']}
+                                />
+                            </div>
+                            <div className={style.body}>
+                                <Text ellipsis strong>{records?.description}</Text>
+                                <div>
+                                    <div>尺寸:{records.width}*{records.height}</div>
+                                    {records.source === 'video' && records.image_duration_millisecond && <div>时长:{formatSecondsToTime(Math.floor(records.image_duration_millisecond / 1000))}</div>}
+                                </div>
+                                <div>
+                                    <Text style={{ fontSize: 12 }}>{formatBytes(records?.file_size)}</Text>
+                                    <div>设计师:{records?.create_name || '--'}</div>
+                                </div>
+                            </div>
+                        </div>
+                    }
+                },
+                {
+                    title: '投手', dataIndex: 'pitcher_name', label: '自定义指标', width: 80, align: 'center', default: 2,
+                    render: (a: string) => a || '--'
+                },
+                {
+                    title: '消耗', dataIndex: 'cost', label: '自定义指标', width: 110, default: 3, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '曝光量', dataIndex: 'view_count', label: '自定义指标', width: 110, default: 4, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '点击量', dataIndex: 'valid_click_count', label: '自定义指标', width: 100, default: 5, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '公众号关注人数(点击归因)', dataIndex: 'from_follow_by_click_uv', label: '自定义指标', width: 85, default: 6, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '公众号关注成本(点击归因)', dataIndex: 'from_follow_by_click_cost', label: '自定义指标', width: 85, default: 7, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '公众号关注人数(平台上报)', dataIndex: 'biz_follow_uv', label: '自定义指标', width: 85, default: 8, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '目标转化量', dataIndex: 'conversions_count', label: '自定义指标', width: 65, default: 9, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '目标转化成本', dataIndex: 'conversions_cost', label: '自定义指标', width: 65, default: 10, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '深度目标转化量', dataIndex: 'deep_conversions_count', label: '自定义指标', width: 65, default: 11, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '加企业微信客服次数', dataIndex: 'scan_follow_count', label: '自定义指标', width: 70, default: 12, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '加企业微信客服人数', dataIndex: 'scan_follow_user_count', label: '自定义指标', width: 70, default: 13, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '下单次数', dataIndex: 'order_pv', label: '自定义指标', width: 60, default: 14, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '下单金额', dataIndex: 'order_amount', label: '自定义指标', width: 80, default: 15, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '下单次数(点击归因)', dataIndex: 'order_by_click_count', label: '自定义指标', width: 75, default: 16, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '下单金额(点击归因)', dataIndex: 'order_by_click_amount', label: '自定义指标', width: 75, default: 17, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '点击广告当天内,下单次数', dataIndex: 'first_day_order_by_click_count', label: '自定义指标', width: 80, default: 18, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '点击首日下单次数(首日新增下单量)', dataIndex: 'first_day_order_count', label: '自定义指标', width: 100, default: 19, align: 'center', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} />
+                },
+                {
+                    title: '首日新增下单金额(点击归因)', dataIndex: 'first_day_order_by_click_amount', label: '自定义指标', width: 95, default: 20, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '点击首日下单金额', dataIndex: 'first_day_order_amount', label: '自定义指标', width: 110, default: 21, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '点击均价', dataIndex: 'cpc', label: '自定义指标', width: 60, default: 22, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '千次曝光成本', dataIndex: 'thousand_display_price', label: '自定义指标', width: 65, default: 23, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '点击率', dataIndex: 'ctr', label: '自定义指标', width: 75, default: 24, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '公众号关注成本(平台上报)', dataIndex: 'biz_follow_cost', label: '自定义指标', width: 85, default: 25, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '公众号关注率(平台上报)', dataIndex: 'biz_follow_rate', label: '自定义指标', width: 80, default: 26, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '目标转化率', dataIndex: 'conversions_rate', label: '自定义指标', width: 75, default: 27, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '深度目标转化率', dataIndex: 'deep_conversions_rate', label: '自定义指标', width: 75, default: 28, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '加企业微信客服成本', dataIndex: 'scan_follow_user_cost', label: '自定义指标', width: 75, default: 29, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '加企业微信客服率', dataIndex: 'scan_follow_user_rate', label: '自定义指标', width: 75, default: 30, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '下单单价', dataIndex: 'order_unit_price', label: '自定义指标', width: 60, default: 31, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '下单率', dataIndex: 'order_rate', label: '自定义指标', width: 70, default: 32, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '下单成本', dataIndex: 'order_by_display_cost', label: '自定义指标', width: 75, default: 33, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '下单ROI', dataIndex: 'order_roi', label: '自定义指标', width: 75, default: 34, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '下单率(点击归因)', dataIndex: 'order_by_click_rate', label: '自定义指标', width: 75, default: 35, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                },
+                {
+                    title: '下单成本(点击归因)', dataIndex: 'order_by_click_cost', label: '自定义指标', width: 75, default: 36, align: 'right', sorter: true,
+                    render: (a: string) => <Statistic value={a || 0} precision={2} />
+                },
+                {
+                    title: '首日新增下单ROI', dataIndex: 'first_day_order_roi', label: '自定义指标', width: 75, default: 37, align: 'center', sorter: true,
+                    render: (a: number) => <Statistic value={a ? a * 100 : 0} precision={2} valueStyle={!a ? {} : a >= 50 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+                }
+            ]
+        },
+    ]
+}
+
+export default columns12

+ 28 - 12
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/addSubAccount.tsx

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
 import { getAccountAllListApi } from "@/services/launchAdq/adAuthorize"
 import { useAjax } from "@/Hook/useAjax"
-import { authAccountAssetsGroupAccountApi, getConversionInfoApi } from "@/services/adqV3/global"
+import { authAccountAssetsGroupAccountApi, batchAuthAccountAssetsGroupAccountApi, getConversionInfoApi } from "@/services/adqV3/global"
 import { GOAL_ROAS_ENUM, OPTIMIZATIONGOAL_ENUM } from "../../tencentAdPutIn/const"
 
 interface Props {
@@ -31,6 +31,7 @@ const AddSubAccount: React.FC<Props> = ({ authType, authMainAccountId, accountAs
 
     const getAccountAllList = useAjax(() => getAccountAllListApi())
     const authAccountAssetsGroupAccount = useAjax((params) => authAccountAssetsGroupAccountApi(params))
+    const batchAuthAccountAssetsGroupAccount = useAjax((params) => batchAuthAccountAssetsGroupAccountApi(params))
     const getConversionInfo = useAjax((params) => getConversionInfoApi(params))
     /************************************/
 
@@ -58,17 +59,30 @@ const AddSubAccount: React.FC<Props> = ({ authType, authMainAccountId, accountAs
         form.validateFields().then(valid => {
             console.log(valid)
             let params = { ...valid, accountAssetsGroupId: accountAssetsGroupId }
-            if (valid.assetId) {
-                const assetName = tableData.find(item => item.value === valid.assetId).name
-                params.assetName = assetName
-            }
-            authAccountAssetsGroupAccount.run(params).then(res => {
-                console.log(res)
-                if (res) {
-                    message.success('授权成功')
-                    onChange?.()
+            if (authType === 'conversion') {
+                if (valid.assetId?.length) {
+                    params.assetsDTOS = valid.assetId.map((id: any) => {
+                        const assetName = tableData.find(item => item.value === id).name
+                        return { assetId: id, assetName }
+                    })
+                    delete valid.assetId
                 }
-            })
+                batchAuthAccountAssetsGroupAccount.run(params).then(res => {
+                    console.log(res)
+                    if (res) {
+                        message.success('授权成功')
+                        onChange?.()
+                    }
+                })
+            } else {
+                authAccountAssetsGroupAccount.run(params).then(res => {
+                    console.log(res)
+                    if (res) {
+                        message.success('授权成功')
+                        onChange?.()
+                    }
+                })
+            }
         })
     }
 
@@ -78,7 +92,7 @@ const AddSubAccount: React.FC<Props> = ({ authType, authMainAccountId, accountAs
         onCancel={onClose}
         className="modalResetCss"
         onOk={handleOk}
-        confirmLoading={authAccountAssetsGroupAccount.loading}
+        confirmLoading={authAccountAssetsGroupAccount.loading || batchAuthAccountAssetsGroupAccount.loading}
     >
         <Form
             name="basicAddSub"
@@ -91,9 +105,11 @@ const AddSubAccount: React.FC<Props> = ({ authType, authMainAccountId, accountAs
                     placeholder="请选择授权资产"
                     allowClear
                     showSearch
+                    mode="multiple"
                     filterOption={(input, option) =>
                         (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }
+                    maxTagCount={5}
                     loading={getConversionInfo.loading}
                     options={tableData}
                     dropdownMatchSelectWidth={false}

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

@@ -402,7 +402,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                             </Space>}
                             className="cardResetCss"
                             key={field.key}
-                            style={{ width: (deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' || dynamicCreativeSwitch) ? ([641, 642, 643, 720, 721, 722, 1529, 618].includes(creativeTemplateId) || dynamicGroup?.length === 1) ? '100%' : 'calc(50% - 5px)' : '100%' }}
+                            style={{ width: (deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' || dynamicCreativeSwitch) ? ([641, 642, 643, 2277, 720, 721, 722, 1529, 618].includes(creativeTemplateId) || dynamicGroup?.length === 1) ? '100%' : 'calc(50% - 5px)' : '100%' }}
                             extra={fields?.length > 1 && <DeleteOutlined className={styles.clear} onClick={() => remove(field.name)} style={{ color: 'red' }} />}
                         >
                             {(deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' || dynamicCreativeSwitch) ? <Space size={30} style={{ width: '100%' }} className={styles.space}>

+ 24 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -26,6 +26,7 @@ import VideoChannel from "../../components/VideoChannel"
 import Save from "./Save"
 import { useLocalStorageState } from "ahooks"
 import SelectAccount from "./SelectAccount"
+import PositionPackage from "../../components/PositionPackage"
 
 export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
 
@@ -47,6 +48,7 @@ const Create: React.FC = () => {
     const [channelsProfileVisible, setChannelsProfileVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
     const [sourceVisible, setSourceVisible] = useState<boolean>(false) // 选择数据源弹窗控制
     const [conversionVisible, setConversionVisible] = useState<boolean>(false) // 选择转化归因控制
+    const [positionPackageVisible, setPositionPackageVisible] = useState<boolean>(false) // 选择优量汇流量包
     const [materialData, setMaterialData] = useState<any>({}) // 素材数据
     const [textData, setTextData] = useState<any>({})
     const [tableData, setTableData] = useState<any>({})
@@ -229,7 +231,7 @@ const Create: React.FC = () => {
 
                             let isConversion = false
                             setAccountCreateLogs(Object.keys(accountIdParamVOMap || {}).map(accountId => {
-                                const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, conversionInfo, wechatChannelVO, wechatAppletList } = accountIdParamVOMap[accountId]
+                                const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, excludeUnionPositionPackages, unionPositionPackages, conversionInfo, wechatChannelVO, wechatAppletList } = accountIdParamVOMap[accountId]
                                 let data: PULLIN.AccountCreateLogsProps = {
                                     accountId: Number(accountId),
                                     productList: productDTOS
@@ -243,6 +245,12 @@ const Create: React.FC = () => {
                                 if (userActionSetsList) {
                                     data.userActionSetsList = userActionSetsList
                                 }
+                                if (excludeUnionPositionPackages) {
+                                    data.excludeUnionPositionPackage = excludeUnionPositionPackages
+                                }
+                                if (unionPositionPackages) {
+                                    data.unionPositionPackage = unionPositionPackages
+                                }
                                 if (conversionInfo) {
                                     isConversion = true
                                     data.newConversionList = [conversionInfo]
@@ -768,12 +776,14 @@ const Create: React.FC = () => {
         }
         let accountIdParamDTOMap: any = {}
         accountCreateLogs.forEach(item => {
-            let { pageList, productList, userActionSetsList, accountId, wechatChannelList, newConversionList, videoChannelList } = item
+            let { pageList, productList, userActionSetsList, unionPositionPackage, excludeUnionPositionPackage, accountId, wechatChannelList, newConversionList, videoChannelList } = item
 
             let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
 
             let map: any = {
                 userActionSetsList: userActionSetsListDto,
+                unionPositionPackage: unionPositionPackage?.map(item => item.unionPackageId),
+                excludeUnionPositionPackage: excludeUnionPositionPackage?.map(item => item.unionPackageId),
             }
             if (!['PAGE_TYPE_WECHAT_MINI_GAME'].includes(dynamic?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType)) {
                 map.pageList = pageList?.map((item: { pageId: any }) => item.pageId)
@@ -815,7 +825,6 @@ const Create: React.FC = () => {
             taskType: putInType,
             ...copyTask
         }
-
         if (values?.submitRule !== 0) {
             createAdgroupTaskV2.run(params).then(res => {
                 if (res) {
@@ -908,6 +917,7 @@ const Create: React.FC = () => {
                             :
                             <Button type="primary" danger={!accountCreateLogs?.some(item => item?.newConversionList?.length)} onClick={() => { setConversionVisible(true) }}>{accountCreateLogs?.some(item => item?.newConversionList?.length) ? <>重新选择转化归因<CheckOutlined style={{ color: '#FFF' }} /></> : '请选择转化归因'}</Button>
                         }
+                        {(siteSet?.includes('SITE_SET_MOBILE_UNION') || automaticSiteEnabled) && <Button onClick={() => { setPositionPackageVisible(true) }}>优量汇流量包(选填){accountCreateLogs?.some(item => item?.unionPositionPackage?.length || item?.excludeUnionPositionPackage?.length) && <CheckOutlined style={{ color: accountCreateLogs?.every(item => item?.unionPositionPackage?.length && item?.excludeUnionPositionPackage?.length) ? '#1890ff' : '#52C41A' }} />}</Button>}
                     </>}
                 </Space>
 
@@ -1018,6 +1028,17 @@ const Create: React.FC = () => {
                         clearData()
                     }}
                 />}
+                {/* 优量汇流量包 */}
+                {positionPackageVisible && <PositionPackage
+                    visible={positionPackageVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setPositionPackageVisible(false)}
+                    onChange={(e) => {
+                        setAccountCreateLogs(e);
+                        setPositionPackageVisible(false);
+                        clearData()
+                    }}
+                />}
             </Card>
         </Spin>
 

+ 2 - 0
src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts

@@ -81,6 +81,8 @@ declare namespace PULLIN {
         coldStartAudienceList?: any[],
         newConversionList?: any[],
         videoChannelList?: any[]
+        unionPositionPackage?: any[]
+        excludeUnionPositionPackage?: any[]
     }
     interface DynamicReactContent {
         form: FormInstance<any>

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

@@ -267,6 +267,18 @@ export async function getRemoteMaterialListApi(data: CLOUDNEW.GetRemoteMaterialL
     })
 }
 
+/**
+ * 云端素材库总计
+ * @param data 
+ * @returns 
+ */
+export async function getRemoteMaterialTotalApi(data: CLOUDNEW.GetRemoteMaterialListProps) {
+    return request(api + `/material/material/getRemoteMaterialListTotal`, {
+        method: 'POST',
+        data
+    })
+}
+
 
 export async function addRemoteMaterialApi(d: { folderId: number, data: CLOUDNEW.AddRemoteMaterialProps[] }) {
     const { folderId, data } = d

+ 26 - 1
src/services/adqV3/global.ts

@@ -191,6 +191,19 @@ export async function getCreativeDetailsApi(data: any) {
 }
 
 
+/**
+ * 获取流量包
+ * @param data 
+ * @returns 
+ */
+export async function getBatchUnionPositionPackagesApi(data: { unionPackageType: 'UNION_PACKAGE_TYPE_INCLUDE' | 'UNION_PACKAGE_TYPE_EXCLUDE', accountId?: number, accountIds?: number[] }) {
+    return request(api + `/adq/v3/marketingAssets/getBatchUnionPositionPackages`, {
+        method: 'POST',
+        data,
+    });
+}
+
+
 /**
  * 获取创意规格列表
  * @param data 
@@ -799,13 +812,25 @@ export async function getAccountAssetsGroupAccountListApi(data: { groupId: numbe
  * @param data 
  * @returns 
  */
-export async function authAccountAssetsGroupAccountApi(data: { accountAssetsGroupId: number, accountId: number[] }) {
+export async function authAccountAssetsGroupAccountApi(data: { accountAssetsGroupId: number, accountId: number[], assetId?: number, assetName?: string }) {
     return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/auth`, {
         method: 'POST',
         data
     })
 }
 
+/**
+ * 批量授权
+ * @param data 
+ * @returns 
+ */
+export async function batchAuthAccountAssetsGroupAccountApi(data: { accountAssetsGroupId: number, accountId: number[], assetsDTOS: { assetId: number, assetName: string }[] }) {
+    return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/batchAuth`, {
+        method: 'POST',
+        data
+    })
+}
+
 /**
  * 同步素材
  * @param data