wjx 2 days ago
parent
commit
32409da5ef

+ 896 - 0
src/pages/launchSystemV3/adqv3/ad/autoAcquisitionSetTask.tsx

@@ -0,0 +1,896 @@
+import { Badge, Button, Card, DatePicker, Drawer, Dropdown, Form, Input, InputNumber, message, Modal, Popconfirm, Radio, Select, Space, Table, Tag, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import style from './index.less'
+import { DownOutlined, QuestionCircleOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { RangePickerProps } from "antd/lib/date-picker"
+import moment from "moment"
+import SetTime from "./setTime"
+import { adAddTaskApi, addOnceTaskAutoAcquisitionApi, addTaskAutoAcquisitionApi, delAdgroupQuantTaskAdApi, delAdgroupQuantTaskApi, getAdgroupQuantTaskAdDetailsListApi, getAdgroupQuantTaskAdLogListApi, getAdgroupQuantTaskListApi, getTaskAllListApi, updateTaskAutoAcquisitionApi } from "@/services/launchAdq/adqv3"
+import { copy } from "@/utils/utils"
+
+interface AutoAcquisitionSetTaskProps {
+    selectAdList: any[]
+    onChange?: (type?: number) => void
+}
+/**
+ * 一键起量任务
+ * @returns 
+ */
+const AutoAcquisitionSetTask: React.FC<AutoAcquisitionSetTaskProps> = ({ selectAdList = [], onChange }) => {
+
+    /*********************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [taskVisible, setTaskVisible] = useState<boolean>(false)
+    const [addTaskVisible, setAddTaskVisible] = useState<boolean>(false)
+    /*********************************/
+
+    return <>
+        <Dropdown
+            menu={{
+                items: [
+                    {
+                        label: <span style={{ display: 'inline-block', width: 120 }}>创建一键起量任务</span>,
+                        key: '1',
+                        onClick: () => { setVisible(true) }
+                    },
+                    {
+                        label: '添加到任务',
+                        key: '2',
+                        disabled: selectAdList.length === 0,
+                        onClick: () => { setAddTaskVisible(true) }
+                    },
+                    {
+                        label: '起量任务列表',
+                        key: '3',
+                        onClick: () => { setTaskVisible(true) }
+                    }
+                ]
+            }}
+            placement="bottomLeft"
+            arrow
+        >
+            <Button>
+                <Space>
+                    一键起量
+                    <DownOutlined />
+                </Space>
+            </Button>
+        </Dropdown>
+
+        {/* 任务创建 */}
+        {visible && <AutoAcquisitionSet
+            selectAdList={selectAdList}
+            visible={visible}
+            onClose={() => {
+                setVisible(false)
+            }}
+            onChange={(val) => {
+                onChange?.(val)
+                setVisible(false)
+            }}
+        />}
+
+        {/* 广告加人到任务 */}
+        {addTaskVisible && <AdAddTask
+            selectAdList={selectAdList}
+            visible={addTaskVisible}
+            onClose={() => {
+                setAddTaskVisible(false)
+            }}
+            onChange={(val) => {
+                setAddTaskVisible(false)
+            }}
+        />}
+
+        {/* 任务列表 */}
+        {taskVisible && <TaskLog
+            visible={taskVisible}
+            onClose={() => setTaskVisible(false)}
+        />}
+    </>;
+};
+
+interface Props {
+    selectAdList?: any[]
+    initialValues?: any
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (type?: number) => void
+}
+
+/**
+ * 设置一键起量任务
+ * @param param0 
+ * @returns 
+ */
+const AutoAcquisitionSet: React.FC<Props> = ({ selectAdList = [], initialValues, visible, onChange, onClose }) => {
+
+    /****************************************/
+    const [form] = Form.useForm<ADQV3.AddTaskAutoAcquisitionProps>();
+    const startDay = Form.useWatch('startDay', form);
+    const endDay = Form.useWatch('endDay', form);
+    const addType = Form.useWatch('addType', form);
+    const taskType = Form.useWatch('taskType', form);
+
+    const addTaskAutoAcquisition = useAjax((params) => addTaskAutoAcquisitionApi(params))
+    const updateTaskAutoAcquisition = useAjax((params) => updateTaskAutoAcquisitionApi(params))
+    const addOnceTaskAutoAcquisition = useAjax((params) => addOnceTaskAutoAcquisitionApi(params))
+    /****************************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+
+            const { taskType, addType, startDay, endDay, budgetPercent, ...val } = values;
+            const params: ADQV3.AddTaskAutoAcquisitionProps = {
+                ...val,
+            }
+            if (addType === 'percent' && budgetPercent) {
+                params.budgetPercent = budgetPercent / 100;
+            }
+
+            if (selectAdList?.length) {
+                params.adgroupBeanList = selectAdList.map(item => ({ accountId: item.accountId, adgroupId: item.adgroupId }))
+            }
+            console.log(values, params);
+            if (initialValues?.id) {
+                params.startDay = moment(startDay).format('YYYY-MM-DD')
+                params.endDay = moment(endDay).format('YYYY-MM-DD')
+                updateTaskAutoAcquisition.run({ ...params, idList: [initialValues.id] }).then(res => {
+                    if (res) {
+                        message.success('修改成功')
+                        onChange?.()
+                    }
+                })
+            } else {
+                if (taskType === 'cyc') { // 循环
+                    params.startDay = moment(startDay).format('YYYY-MM-DD')
+                    params.endDay = moment(endDay).format('YYYY-MM-DD')
+                    addTaskAutoAcquisition.run(params).then(res => {
+                        if (res) {
+                            message.success('添加成功')
+                            onChange?.()
+                        }
+                    })
+                } else { // 立即执行
+                    addOnceTaskAutoAcquisition.run(params).then(res => {
+                        if (res) {
+                            message.success('添加成功')
+                            onChange?.(1)
+                        }
+                    })
+                }
+            }
+        }).catch(() => {
+            form.submit()
+        });
+    }
+
+    const disabledDateStart: RangePickerProps['disabledDate'] = current => {
+        // Can not select days before today and today
+        if (endDay) {
+            return current && current < moment().startOf('day') || current > moment(endDay) || current < moment(endDay).subtract(10, 'days');
+        }
+        return current && current < moment().startOf('day');
+    };
+
+    const disabledDateEnd: RangePickerProps['disabledDate'] = current => {
+        // Can not select days before today and today
+        if (startDay) {
+            return current && current < moment().startOf('day') || current < moment(startDay) || current > moment(startDay).add(10, 'days');
+        }
+        return current && current < moment().startOf('day');
+    };
+
+    return <Modal
+        title={<strong>{initialValues?.id ? '修改' : '新增'}一键起量任务
+            <Tooltip title={<div>
+                <p>1. 一键起量期间产生的消耗不赔付,但转化计入赔付门槛判断;</p>
+                <p>2. 一键起量可能导致转化成本高于预期,且起量结束后不一定能持续消耗。</p>
+            </div>}>
+                <QuestionCircleOutlined />
+            </Tooltip>
+        </strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        className='modalResetCss'
+        width={750}
+        confirmLoading={addTaskAutoAcquisition.loading || addOnceTaskAutoAcquisition.loading || updateTaskAutoAcquisition.loading}
+    >
+        <div className={style.autoAcquisitionSet}>
+            {selectAdList?.length > 0 && <div className={style.left}>
+                <Table
+                    dataSource={selectAdList}
+                    size="small"
+                    scroll={{ x: 400, y: 450 }}
+                    bordered
+                    rowKey={'adgroupId'}
+                    pagination={false}
+                    columns={[
+                        {
+                            title: '广告名称',
+                            dataIndex: 'adgroupName',
+                            key: 'adgroupName',
+                            width: 200,
+                            render(value) {
+                                return <span style={{ wordBreak: 'break-all' }}>{value}</span>
+                            },
+                        },
+                        {
+                            title: '原设置',
+                            dataIndex: 'beforeSet',
+                            key: 'beforeSet',
+                            width: 120,
+                            render(value, record) {
+                                if (!record?.autoAcquisitionEnabled) {
+                                    return '未开启'
+                                }
+                                return `一键起量中,起量预算:${record?.autoAcquisitionBudget} 元`
+                            },
+                        }
+                    ]}
+                />
+            </div>}
+            <div className={style.right}>
+                <div className={style.edit}>
+                    <div>
+                        <Form
+                            form={form}
+                            name="newTaks"
+                            layout={selectAdList?.length > 0 ? 'vertical' : 'horizontal'}
+                            labelCol={selectAdList?.length > 0 ? undefined : { span: 5 }}
+                            labelAlign="left"
+                            colon={false}
+                            scrollToFirstError={{
+                                behavior: 'smooth',
+                                block: 'center'
+                            }}
+                            onFinishFailed={({ errorFields }) => {
+                                message.error(errorFields?.[0]?.errors?.[0])
+                            }}
+                            onFinish={handleOk}
+                            initialValues={initialValues?.id ? initialValues : { addType: 'fixed', taskType: 'cyc' }}
+                        >
+                            <Form.Item label={<strong>任务类型</strong>} name="taskType" rules={[{ required: true, message: '请输入任务类型!' }]}>
+                                <Radio.Group buttonStyle="solid">
+                                    <Radio.Button value="cyc">循环执行</Radio.Button>
+                                    <Radio.Button value="imm" disabled={selectAdList.length === 0 || initialValues?.id}>立即执行</Radio.Button>
+                                </Radio.Group>
+                            </Form.Item>
+                            <Form.Item label={<strong>任务名称</strong>} name="taskName" rules={[{ required: true, message: '请输入任务名称!' }]}>
+                                <Input placeholder="请输入任务名称" />
+                            </Form.Item>
+                            {taskType === 'cyc' && <>
+                                <Form.Item label={<strong>执行日期</strong>} required>
+                                    <Space>
+                                        <Form.Item name="startDay" rules={[{ required: true, message: '请选择开始日期!' }]} noStyle>
+                                            <DatePicker placeholder="开始日期" disabledDate={disabledDateStart} />
+                                        </Form.Item>
+                                        <span>-</span>
+                                        <Form.Item name="endDay" rules={[{ required: true, message: '请选择结束日期!' }]} noStyle>
+                                            <DatePicker placeholder="结束日期" disabledDate={disabledDateEnd} />
+                                        </Form.Item>
+                                    </Space>
+                                </Form.Item>
+                                <Form.Item label={<strong>执行时间</strong>} name="timeList" rules={[{ required: true, message: '请添加执行时间!' }]}>
+                                    <SetTime />
+                                </Form.Item>
+                            </>}
+                            <Form.Item label={<strong>修改预算类型</strong>} name="addType" rules={[{ required: true, message: '请选择修改预算类型!' }]}>
+                                <Radio.Group buttonStyle="solid">
+                                    <Radio.Button value="fixed">固定值</Radio.Button>
+                                    <Radio.Button value="percent">百分比上下浮动修改</Radio.Button>
+                                </Radio.Group>
+                            </Form.Item>
+                            {addType === 'fixed' ? <Form.Item label={<strong>一键起量预算</strong>} name="budget">
+                                <InputNumber placeholder="起量预算,建议设置为出价的10倍" min={200} max={100000} style={{ width: '100%' }} />
+                            </Form.Item> : <>
+                                <Form.Item label={<strong>默认起量预算</strong>} name="defaultBudget" rules={[{ required: true, message: '请输入默认起量预算!' }]}>
+                                    <InputNumber placeholder="默认起量预算,建议设置为出价的10倍" min={200} max={100000} style={{ width: '100%' }} />
+                                </Form.Item>
+                                <Form.Item label={<strong>起量预算百分比</strong>} name="budgetPercent" rules={[{ required: true, message: '请输入起量预算百分比!' }]}>
+                                    <InputNumber placeholder="起量预算,原有基础上下调百分比" style={{ width: '100%' }} addonAfter="%" />
+                                </Form.Item>
+                            </>}
+                        </Form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </Modal>
+}
+
+interface AdAddTaskProps {
+    selectAdList?: any[]
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (type?: number) => void
+}
+
+const AdAddTask: React.FC<AdAddTaskProps> = ({ selectAdList = [], visible, onClose, onChange }) => {
+
+    /*************************************/
+    const [form] = Form.useForm();
+    const [taskList, setTaskList] = useState<{ label: string, value: number }[]>([])
+
+    const getTaskAllList = useAjax(() => getTaskAllListApi())
+    const adAddTask = useAjax((params) => adAddTaskApi(params))
+    /*************************************/
+
+    useEffect(() => {
+        getTaskAllList.run().then(res => {
+            if (res) {
+                setTaskList(Object.keys(res).map(key => ({ label: res[key], value: Number(key) })))
+            }
+        })
+    }, [])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            adAddTask.run({ ...values, adgroupBeanList: selectAdList.map(item => ({ accountId: item.accountId, adgroupId: item.adgroupId })) }).then(res => {
+                if (res) {
+                    message.success('添加成功')
+                    onChange?.(1)
+                }
+            })
+        })
+    }
+
+    return <Modal
+        title={<strong>添加至一键起量任务</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        className='modalResetCss'
+        width={600}
+    >
+        <Form
+            form={form}
+            name="addTaks"
+            labelCol={{ span: 5 }}
+            labelAlign="left"
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+        >
+            <Form.Item label={<strong>任务</strong>} name="taskId" rules={[{ required: true, message: '请一键起量任务!' }]}>
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="一键起量任务"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                    }
+                    options={taskList}
+                />
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+interface TaskLogProps {
+    visible?: boolean
+    onClose?: () => void
+}
+const TaskLog: React.FC<TaskLogProps> = ({ visible, onClose }) => {
+
+    /******************************************/
+    const [queryForm, setQueryForm] = useState<ADQV3.GetAdgroupQuantTaskListProps>({ pageNum: 1, pageSize: 20 })
+    const [adLogData, setAdLogData] = useState<{ taskData: any, visible?: boolean }>({ taskData: {}, visible: false })
+    const [adDetailsData, setAdDetailsData] = useState<{ taskData: any, visible?: boolean }>({ taskData: {}, visible: false })
+    const [adEditData, setAdEditData] = useState<{ initialValues: any, visible?: boolean }>({ initialValues: {}, visible: false })
+    const getAdgroupQuantTaskList = useAjax((params) => getAdgroupQuantTaskListApi(params))
+    const delAdgroupQuantTask = useAjax((params) => delAdgroupQuantTaskApi(params))
+    /******************************************/
+
+    useEffect(() => {
+        getAdgroupQuantTaskList.run(queryForm)
+    }, [queryForm])
+
+    const handleDel = (id: number) => {
+        const hide = message.loading('正在删除...', 0)
+        delAdgroupQuantTask.run({ id }).then((res) => {
+            hide()
+            if (res) {
+                message.success('删除成功')
+                getAdgroupQuantTaskList.refresh()
+            }
+        }).catch(() => hide())
+    }
+
+    return <Drawer
+        title={<strong>一键起量任务列表</strong>}
+        open={visible}
+        onClose={onClose}
+        width={1200}
+        headerStyle={{ padding: '10px 16px' }}
+        maskClosable={false}
+        className={`modalResetCss targetingSelect`}
+    >
+        <Card className="cardResetCss">
+            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
+                <div style={{ display: 'flex', gap: 8 }}>
+                    <Input.Search placeholder="任务名称" style={{ width: 200 }} onSearch={(val) => setQueryForm({ ...queryForm, taskName: val, pageNum: 1 })} allowClear enterButton />
+                    <Input.Search
+                        placeholder="广告ID(多个,,空格换行)"
+                        style={{ width: 250 }}
+                        onSearch={(val) => {
+                            let valArr: any = []
+                            if (val) {
+                                valArr = val.replace(/[,,\s]/g, ',').split(',').filter((a: any) => a)
+                            }
+                            setQueryForm({ ...queryForm, adgroupIdList: valArr, pageNum: 1 })
+                        }}
+                        allowClear
+                        enterButton
+                    />
+                    <DatePicker placeholder="开始日期" value={queryForm?.startDay ? moment(queryForm.startDay) : undefined} onChange={(_, dateString) => setQueryForm({ ...queryForm, startDay: dateString, pageNum: 1 })} />
+                    <DatePicker placeholder="结束日期" value={queryForm?.endDay ? moment(queryForm.endDay) : undefined} onChange={(_, dateString) => setQueryForm({ ...queryForm, endDay: dateString, pageNum: 1 })} />
+                </div>
+                <Table
+                    size={'small'}
+                    bordered
+                    dataSource={getAdgroupQuantTaskList?.data?.records}
+                    rowKey={'id'}
+                    scroll={{ y: 400 }}
+                    pagination={{
+                        pageSize: getAdgroupQuantTaskList?.data?.size || 50,
+                        current: getAdgroupQuantTaskList?.data?.current || 1,
+                        showTotal: total => `总共 ${total} 数据`,
+                        total: getAdgroupQuantTaskList?.data?.total,
+                        showSizeChanger: true,
+                        showLessItems: true,
+                        defaultCurrent: 1,
+                        defaultPageSize: 50,//默认初始的每页条数
+                        onChange: (page, pageSize) => {
+                            setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                        }
+                    }}
+                    loading={getAdgroupQuantTaskList.loading}
+                    columns={[
+                        {
+                            title: '操作',
+                            dataIndex: 'cz',
+                            key: 'cz',
+                            width: 135,
+                            align: 'center',
+                            fixed: 'left',
+                            render(_, record) {
+                                return <div style={{ display: 'flex', gap: 10 }}>
+                                    <a style={{ fontSize: 12 }} onClick={() => {
+                                        setAdDetailsData({ taskData: record, visible: true })
+                                    }}>详情</a>
+                                    <a style={{ fontSize: 12 }} onClick={() => {
+                                        setAdLogData({ taskData: record, visible: true })
+                                    }}>执行日志</a>
+                                    {record?.type === 1 && <a style={{ fontSize: 12 }} onClick={() => {
+                                        setAdEditData({
+                                            initialValues: {
+                                                id: record.id,
+                                                taskType: record.type === 1 ? 'cyc' : 'imm',
+                                                taskName: record.taskName,
+                                                startDay: record?.startDay ? moment(record.startDay) : undefined,
+                                                endDay: record?.endDay ? moment(record.endDay) : undefined,
+                                                timeList: record.timeList,
+                                                addType: record?.budget ? 'fixed' : 'percent',
+                                                budget: record?.budget || 0,
+                                                defaultBudget: record?.defaultBudget || 0,
+                                                budgetPercent: record?.budgetPercent ? record.budgetPercent * 100 : 0,
+                                            }, visible: true
+                                        })
+                                    }}>修改</a>}
+                                    <Popconfirm
+                                        title="确定删除?"
+                                        onConfirm={() => handleDel(record.id)}
+                                    >
+                                        <a style={{ color: 'red', fontSize: 12 }}>删除</a>
+                                    </Popconfirm>
+                                </div>
+                            }
+                        },
+                        {
+                            title: '任务名称',
+                            dataIndex: 'taskName',
+                            key: 'taskName',
+                            width: 80,
+                            align: 'center',
+                            render(value) {
+                                return <span style={{ fontSize: 12 }}>{value}</span>
+                            }
+                        },
+                        {
+                            title: '任务类型',
+                            dataIndex: 'type',
+                            key: 'type',
+                            width: 100,
+                            align: 'center',
+                            render(value) {
+                                return value === 1 ? <Tag color="magenta"><span style={{ fontSize: 12 }}>循环任务</span></Tag> : <Tag color="red"><span style={{ fontSize: 12 }}>立即执行</span></Tag>
+                            }
+                        },
+                        {
+                            title: '任务状态',
+                            dataIndex: 'status',
+                            key: 'status',
+                            align: 'center',
+                            width: 100,
+                            render(value) {
+                                return value === 0 ? <Badge status="warning" text={<span style={{ fontSize: 12 }}>等待执行</span>} /> :
+                                    value === 1 ? <Badge status="processing" text={<span style={{ fontSize: 12 }}>正在执行</span>} /> :
+                                        value === 2 ? <Badge status="success" text={<span style={{ fontSize: 12 }}>执行完成</span>} /> : '--'
+                            }
+                        },
+                        {
+                            title: '执行日期',
+                            dataIndex: 'startDay',
+                            key: 'startDay',
+                            width: 150,
+                            ellipsis: true,
+                            render(value, record) {
+                                return <span style={{ fontSize: 12 }}>{value ? value + '~' + record.endDay : '--'}</span>
+                            }
+                        },
+                        {
+                            title: '执行时间',
+                            dataIndex: 'timeList',
+                            key: 'timeList',
+                            width: 100,
+                            align: 'center',
+                            ellipsis: true,
+                            render(value) {
+                                return <span style={{ fontSize: 12 }}>{value?.length > 0 ? value.toString() : '--'}</span>
+                            }
+                        },
+                        {
+                            title: '一键起量预算',
+                            dataIndex: 'budget',
+                            key: 'budget',
+                            width: 150,
+                            render(value, record) {
+                                return <span style={{ fontSize: 12 }}>{value ? value + '元' : record?.budgetPercent ? (record?.budgetPercent * 100 + `% 默认${record?.defaultBudget}元`) : '--'}</span>
+                            }
+                        },
+                        {
+                            title: '关联广告个数',
+                            dataIndex: 'adCount',
+                            key: 'adCount',
+                            width: 85,
+                            align: 'center',
+                            render(value) {
+                                return <span style={{ fontSize: 12, color: value > 0 ? '#52C41A' : 'red' }}>{(value || value === 0) ? value : '--'}</span>
+                            }
+                        }
+                    ]}
+                />
+            </div>
+        </Card>
+
+        {/* 广告执行记录 */}
+        {adLogData?.visible && <TaskAdLog
+            {...adLogData}
+            onClose={() => {
+                setAdLogData({ visible: false, taskData: {} })
+            }}
+        />}
+
+        {/* 详情 */}
+        {adDetailsData?.visible && <TaskAdDetails
+            {...adDetailsData}
+            onClose={() => {
+                setAdDetailsData({ visible: false, taskData: {} })
+            }}
+        />}
+
+        {/* 修改 */}
+        {adEditData?.visible && <AutoAcquisitionSet
+            {...adEditData}
+            onChange={() => {
+                getAdgroupQuantTaskList?.refresh()
+                setAdEditData({ visible: false, initialValues: {} })
+            }}
+            onClose={() => {
+                setAdEditData({ visible: false, initialValues: {} })
+            }}
+        />}
+    </Drawer>
+}
+
+interface TaskAdLogProps {
+    taskData: any
+    visible?: boolean
+    onClose?: () => void
+}
+
+/**
+ * 广告执行日志
+ * @param param0 
+ */
+const TaskAdLog: React.FC<TaskAdLogProps> = ({ taskData, visible, onClose }) => {
+
+    /************************************/
+    const [queryForm, setQueryForm] = useState<ADQV3.GetAdgroupQuantTaskAdLogListProps>({ pageNum: 1, pageSize: 20, taskId: taskData.id })
+    const getAdgroupQuantTaskAdLogList = useAjax((params) => getAdgroupQuantTaskAdLogListApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getAdgroupQuantTaskAdLogList.run(queryForm)
+    }, [queryForm])
+
+    return <Modal
+        title={<strong>{taskData.taskName} 广告执行日志</strong>}
+        open={visible}
+        onCancel={onClose}
+        className='modalResetCss'
+        width={1000}
+    >
+        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
+            <div style={{ display: 'flex', gap: 8 }}>
+                <Input.Search placeholder="媒体账号" style={{ width: 160 }} onSearch={(val) => setQueryForm({ ...queryForm, accountId: val, pageNum: 1 })} allowClear enterButton />
+                <Input.Search placeholder="广告ID" style={{ width: 160 }} onSearch={(val) => setQueryForm({ ...queryForm, adgroupId: val, pageNum: 1 })} allowClear enterButton />
+                <DatePicker placeholder="开始日期" style={{ width: 130 }} value={queryForm?.startDay ? moment(queryForm.startDay) : undefined} onChange={(_, dateString) => setQueryForm({ ...queryForm, startDay: dateString, pageNum: 1 })} />
+                <DatePicker placeholder="结束日期" style={{ width: 130 }} value={queryForm?.endDay ? moment(queryForm.endDay) : undefined} onChange={(_, dateString) => setQueryForm({ ...queryForm, endDay: dateString, pageNum: 1 })} />
+                <Select
+                    style={{ width: 110 }}
+                    showSearch
+                    allowClear
+                    placeholder="执行状态"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm.status}
+                    onChange={(e) => setQueryForm({ ...queryForm, status: e, pageNum: 1 })}
+                    options={[{ label: '成功', value: 0 }, { label: '失败', value: 1 }]}
+                />
+            </div>
+            <Table
+                size={'small'}
+                bordered
+                dataSource={getAdgroupQuantTaskAdLogList?.data?.records}
+                rowKey={'id'}
+                scroll={{ y: 400 }}
+                pagination={{
+                    pageSize: getAdgroupQuantTaskAdLogList?.data?.size || 50,
+                    current: getAdgroupQuantTaskAdLogList?.data?.current || 1,
+                    showTotal: total => `总共 ${total} 数据`,
+                    total: getAdgroupQuantTaskAdLogList?.data?.total,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 50,//默认初始的每页条数
+                    onChange: (page, pageSize) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                    }
+                }}
+                loading={getAdgroupQuantTaskAdLogList.loading}
+                columns={[
+                    {
+                        title: '起量操作',
+                        dataIndex: 'enabled',
+                        key: 'enabled',
+                        width: 80,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value ? '开启' : '关闭'}</span>
+                        }
+                    },
+                    {
+                        title: '媒体账号',
+                        dataIndex: 'accountId',
+                        key: 'accountId',
+                        width: 80,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '广告ID',
+                        dataIndex: 'adgroupId',
+                        key: 'adgroupId',
+                        width: 96,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '广告名称',
+                        dataIndex: 'adgroupName',
+                        key: 'adgroupName',
+                        width: 180,
+                        ellipsis: true,
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '执行时间',
+                        dataIndex: 'createTime',
+                        key: 'createTime',
+                        width: 135,
+                        ellipsis: true,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '执行状态',
+                        dataIndex: 'status',
+                        key: 'status',
+                        width: 90,
+                        align: 'center',
+                        render(value) {
+                            return value === 0 ? <Tag color='success'><span style={{ fontSize: 12 }}>成功</span></Tag> : <Tag color="error"><span style={{ fontSize: 12 }}>失败</span></Tag>
+                        }
+                    },
+                    {
+                        title: '结果消息',
+                        dataIndex: 'msg',
+                        key: 'msg',
+                        width: 250,
+                        ellipsis: true,
+                        render(value) {
+                            return <a style={{ fontSize: 12 }} onClick={() => copy(value)}>{value}</a>
+                        }
+                    }
+                ]}
+            />
+        </div>
+    </Modal>
+}
+
+/**
+ * 任务下广告详情
+ * @param param0 
+ * @returns 
+ */
+const TaskAdDetails: React.FC<TaskAdLogProps> = ({ taskData, visible, onClose }) => {
+
+    /************************************/
+    const [queryForm, setQueryForm] = useState<ADQV3.GetAdgroupQuantTaskAdDetailsListProps>({ pageNum: 1, pageSize: 20, taskId: taskData.id })
+    const [selectRowData, setSelectRowData] = useState<any[]>([])
+    const getAdgroupQuantTaskAdDetailsList = useAjax((params) => getAdgroupQuantTaskAdDetailsListApi(params))
+    const delAdgroupQuantTaskAd = useAjax((params) => delAdgroupQuantTaskAdApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getAdgroupQuantTaskAdDetailsList.run(queryForm)
+    }, [queryForm])
+
+    const handleDel = () => {
+        delAdgroupQuantTaskAd.run({
+            taskId: taskData.id,
+            adgroupBeanList: selectRowData.map(item => ({ accountId: item.accountId, adgroupId: item.adgroupId }))
+        }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getAdgroupQuantTaskAdDetailsList.refresh()
+            }
+        })
+    }
+
+    return <Modal
+        title={<strong>{taskData.taskName} 广告执行日志</strong>}
+        open={visible}
+        onCancel={onClose}
+        className='modalResetCss'
+        width={800}
+    >
+        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
+            <div style={{ display: 'flex', gap: 8 }}>
+                <Input.Search placeholder="媒体账号" style={{ width: 180 }} onSearch={(val) => setQueryForm({ ...queryForm, accountId: val, pageNum: 1 })} allowClear enterButton />
+                <Input.Search placeholder="广告ID" style={{ width: 180 }} onSearch={(val) => setQueryForm({ ...queryForm, adgroupId: val, pageNum: 1 })} allowClear enterButton />
+                {taskData.type === 1 && <Button type="primary" danger style={{ height: 32, padding: '4px 15px' }} loading={delAdgroupQuantTaskAd.loading} disabled={selectRowData?.length === 0} onClick={handleDel}>删除</Button>}
+            </div>
+            <Table
+                size={'small'}
+                bordered
+                dataSource={getAdgroupQuantTaskAdDetailsList?.data?.records}
+                rowKey={'id'}
+                scroll={{ y: 400 }}
+                pagination={{
+                    pageSize: getAdgroupQuantTaskAdDetailsList?.data?.size || 50,
+                    current: getAdgroupQuantTaskAdDetailsList?.data?.current || 1,
+                    showTotal: total => `总共 ${total} 数据`,
+                    total: getAdgroupQuantTaskAdDetailsList?.data?.total,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 50,//默认初始的每页条数
+                    onChange: (page, pageSize) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                    }
+                }}
+                loading={getAdgroupQuantTaskAdDetailsList.loading}
+                columns={[
+                    {
+                        title: '媒体账号',
+                        dataIndex: 'accountId',
+                        key: 'accountId',
+                        width: 80,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '广告ID',
+                        dataIndex: 'adgroupId',
+                        key: 'adgroupId',
+                        width: 96,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '广告名称',
+                        dataIndex: 'adgroupName',
+                        key: 'adgroupName',
+                        width: 180,
+                        ellipsis: true,
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    },
+                    {
+                        title: '加入任务时间',
+                        dataIndex: 'createTime',
+                        key: 'createTime',
+                        width: 135,
+                        ellipsis: true,
+                        align: 'center',
+                        render(value) {
+                            return <span style={{ fontSize: 12 }}>{value}</span>
+                        }
+                    }
+                ]}
+                rowSelection={taskData.type === 1 ? {
+                    selectedRowKeys: selectRowData?.map((item: any) => item.id),
+                    onSelect: (record: { id: number, mpName: string }, selected: boolean) => {
+                        if (selected) {
+                            selectRowData.push({ ...record })
+                            setSelectRowData([...selectRowData])
+                        } else {
+                            let newSelectAccData = selectRowData.filter((item: { id: number }) => item.id !== record.id)
+                            setSelectRowData([...newSelectAccData])
+                        }
+                    },
+                    onSelectAll: (selected: boolean, selectedRows: { id: number }[], changeRows: { id: number }[]) => {
+                        if (selected) {
+                            let newSelectAccData = [...selectRowData]
+                            changeRows.forEach((item: { id: number }) => {
+                                let index = newSelectAccData.findIndex((ite: { id: number }) => ite.id === item.id)
+                                if (index === -1) {
+                                    newSelectAccData.push({ ...item })
+                                }
+                            })
+                            setSelectRowData([...newSelectAccData])
+                        } else {
+                            let newSelectAccData = selectRowData.filter((item: { id: number }) => {
+                                let index = changeRows.findIndex((ite: { id: number }) => ite.id === item.id)
+                                if (index !== -1) {
+                                    return false
+                                } else {
+                                    return true
+                                }
+                            })
+                            setSelectRowData([...newSelectAccData])
+                        }
+                    }
+                } : undefined}
+            />
+        </div>
+    </Modal>
+}
+
+export default React.memo(AutoAcquisitionSetTask);

+ 2 - 1
src/pages/launchSystemV3/adqv3/ad/index.less

@@ -6,7 +6,8 @@
     }
 
     .right {
-        width: 40%;
+        flex: auto;
+        overflow: hidden;
         border: 1px solid #f0f0f0;
         display: flex;
         flex-direction: column;

+ 9 - 0
src/pages/launchSystemV3/adqv3/ad/index.tsx

@@ -17,6 +17,7 @@ import UserTactics from "../../tencentAdPutIn/create/TacticsS/userTactics";
 import UpdateAd3 from "./updateAd3";
 import { useLocalStorageState } from "ahooks";
 import AutoAcquisitionSet from "./autoAcquisitionSet";
+import AutoAcquisitionSetTask from "./autoAcquisitionSetTask";
 const { Text } = Typography;
 
 const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
@@ -329,6 +330,14 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                                 </Space>
                             </Button>
                         </Dropdown></Col>
+                        <Col>
+                            <AutoAcquisitionSetTask 
+                                selectAdList={selectedRows}
+                                onChange={(val) => {
+                                    if (val) getAdqV3AdList.refresh()
+                                }}
+                            />
+                        </Col>
                     </> : handleType === 2 ? <>
                         <Col><UserTactics
                             type="updateAd"

+ 116 - 0
src/pages/launchSystemV3/adqv3/ad/setTime.tsx

@@ -0,0 +1,116 @@
+import { Button, DatePicker, Form, message, Modal, Popconfirm, Table } from 'antd';
+import React, { useState } from 'react';
+import '../../tencentAdPutIn/index.less'
+import moment from 'moment';
+
+interface SetConfigProps {
+    value?: string[]
+    onChange?: (value: string[]) => void
+}
+
+const SetTime: React.FC<SetConfigProps> = ({ value = [], onChange }) => {
+
+    /*************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    /*************************************/
+
+    return <div>
+        <Button type="primary" onClick={() => setVisible(true)}>添加执行时间</Button>
+        {value?.length > 0 && <Table
+            style={{ marginTop: 10 }}
+            dataSource={value.map((item, index) => ({ time: item, id: index + 1 }))}
+            columns={[
+                {
+                    title: '执行时间',
+                    dataIndex: 'time',
+                    key: 'time',
+                    align: 'center',
+                    width: 100
+                },
+                {
+                    title: '操作',
+                    dataIndex: 'cz',
+                    key: 'cz',
+                    render(_, record) {
+                        return <Popconfirm
+                            title="确定删除该项?"
+                            onConfirm={() => {
+                                onChange?.(value?.filter((_, index) => record.id !== (index + 1)))
+                            }}
+                        >
+                            <a>删除</a>
+                        </Popconfirm>
+                    },
+                },
+            ]}
+            rowKey={'id'}
+            size='small'
+            bordered
+            pagination={false}
+        />}
+        {visible && <SetConfigModal
+            visible={visible}
+            onClose={() => setVisible(false)}
+            onChange={(newValue) => {
+                setVisible(false)
+                const oldValue = JSON.parse(JSON.stringify(value || []))
+                oldValue.push(newValue)
+                const set = new Set(oldValue)
+                onChange?.([...set] as string[])
+            }}
+        />}
+    </div>
+};
+
+interface SetConfigModalProps {
+    onChange?: (value: string) => void
+    visible?: boolean;
+    onClose?: () => void;
+}
+const SetConfigModal: React.FC<SetConfigModalProps> = ({ visible, onChange, onClose }) => {
+
+    /***********************************/
+    const [form] = Form.useForm();
+    /***********************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            onChange?.(moment(values.time).format('HH:mm'))
+        })
+    }
+
+    return <Modal
+        title={<strong>添加执行时间</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        className='modalResetCss'
+    >
+        <Form
+            form={form}
+            name="newConfig"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+        >
+            <Form.Item label={<strong>执行时间</strong>} name="time" rules={[{ required: true, message: '请选择充值类型!' }]}>
+                <DatePicker
+                    picker={'time'}
+                    format={'HH:mm'}
+                    minuteStep={15}
+                    style={{ width: '100%' }}
+                />
+            </Form.Item>
+        </Form>
+    </Modal>
+};
+
+export default React.memo(SetTime);

+ 54 - 0
src/pages/launchSystemV3/adqv3/typings.d.ts

@@ -81,4 +81,58 @@ declare namespace ADQV3 {
         },
         skipExisting: boolean
     }
+    interface AddTaskAutoAcquisitionProps {
+        taskName: string,
+        timeList?: string[]
+        startDay?: string,
+        endDay?: string,
+        adgroupBeanList?: {
+            accountId: number,
+            adgroupId: number
+        }[],
+        budget?: number
+        budgetPercent?: number
+        defaultBudget?: number,
+        addType?: 'fixed' | 'percent',
+        taskType?: 'imm' | 'cyc'
+    }
+    interface GetAdgroupQuantTaskListProps {
+        pageNum: number,
+        pageSize: number,
+        startDay?: string,
+        endDay?: string,
+        taskName?: string,
+        adgroupIdList?: number[]
+    }
+    interface GetAdgroupQuantTaskAdLogListProps {
+        pageNum: number,
+        pageSize: number,
+        taskId: number,
+        accountId?: string
+        adgroupId?: string
+        startDay?: string,
+        endDay?: string,
+        status?: 0 | 1
+    }
+    interface GetAdgroupQuantTaskAdDetailsListProps {
+        pageNum: number,
+        pageSize: number,
+        taskId: number,
+        accountId?: string
+        adgroupId?: string
+    }
+    interface DelAdgroupQuantTaskAdProps {
+        taskId: number,
+        adgroupBeanList: {
+            accountId: number,
+            adgroupId: number
+        }[]
+    }
+    interface AdAddTaskProps {
+        adgroupBeanList: {
+            accountId: number,
+            adgroupId: number
+        }[]
+        taskId: number
+    }
 }

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

@@ -191,4 +191,124 @@ export async function dynamicCreativeLogApi(data: ADQV3.DynamicCreativeLogProps)
         method: 'POST',
         data
     });
+}
+
+/**
+ * 新增任务
+ * @param data 
+ * @returns 
+ */
+export async function addTaskAutoAcquisitionApi(data: ADQV3.AddTaskAutoAcquisitionProps) {
+    return request(api + '/adq/adgroupQuant/task/add', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 修改
+ * @param data 
+ * @returns 
+ */
+export async function updateTaskAutoAcquisitionApi(data: ADQV3.AddTaskAutoAcquisitionProps) {
+    return request(api + '/adq/adgroupQuant/task/modifyById', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 立即执行
+ * @param data 
+ * @returns 
+ */
+export async function addOnceTaskAutoAcquisitionApi(data: ADQV3.AddTaskAutoAcquisitionProps) {
+    return request(api + '/adq/adgroupQuant/task/add/once', {
+        method: 'POST',
+        data
+    });
+}
+
+
+/**
+ * 查询一键起量任务列表
+ * @param data 
+ * @returns 
+ */
+export async function getAdgroupQuantTaskListApi(data: ADQV3.GetAdgroupQuantTaskListProps) {
+    return request(api + '/adq/adgroupQuant/task/listOfPage', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 删除任务
+ * @param params 
+ * @returns 
+ */
+export async function delAdgroupQuantTaskApi(params: {id: number}) {
+    return request(api + '/adq/adgroupQuant/task/delete', {
+        method: 'DELETE',
+        params
+    });
+}
+
+
+/**
+ * 一键起量广告日志-分页查询任务的广告执行日志
+ * @param data 
+ * @returns 
+ */
+export async function getAdgroupQuantTaskAdLogListApi(data: ADQV3.GetAdgroupQuantTaskAdLogListProps) {
+    return request(api + '/adq/adgroupQuant/adLog/listOfPage', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 一键起量广告-从任务中批量删除广告
+ * @param data 
+ * @returns 
+ */
+export async function delAdgroupQuantTaskAdApi(data: ADQV3.DelAdgroupQuantTaskAdProps) {
+    return request(api + '/adq/adgroupQuant/ad/removeFromTask', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 一键起量广告-分页查询任务关联的广告列表
+ * @param data 
+ * @returns 
+ */
+export async function getAdgroupQuantTaskAdDetailsListApi(data: ADQV3.GetAdgroupQuantTaskAdDetailsListProps) {
+    return request(api + '/adq/adgroupQuant/ad/listOfPage', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 所有任务列表
+ * @returns 
+ */
+export async function getTaskAllListApi() {
+    return request(api + '/adq/adgroupQuant/task/all', {
+        method: 'GET'
+    });
+}
+
+/**
+ * 添加到任务
+ * @param data 
+ * @returns 
+ */
+export async function adAddTaskApi(data: ADQV3.AdAddTaskProps) {
+    return request(api + '/adq/adgroupQuant/ad/addToTask', {
+        method: 'POST',
+        data
+    });
 }