wjx il y a 1 an
Parent
commit
c35b28bf4c

+ 6 - 0
config/routerConfig.ts

@@ -202,6 +202,12 @@ const gameDataStatistics = {
                     name: '腾讯广告监控',
                     access: 'tencentMonitor',
                     component: './gameDataStatistics/adlist/tencentMonitor',
+                },
+                {
+                    path: '/gameDataStatistics/adlist/strategy',
+                    name: '游戏广告策略',
+                    access: 'strategy',
+                    component: './gameDataStatistics/adlist/strategy',
                 }
             ]
         },

+ 1 - 0
src/global.less

@@ -289,6 +289,7 @@ body {
 .ant-picker-panel-container,
 .ant-input-number-input,
 .ant-input-number,
+.ant-input-number-group-addon,
 .ant-select-dropdown {
   border-radius: 6px !important;
 }

+ 411 - 0
src/pages/gameDataStatistics/adlist/strategy/configModal.tsx

@@ -0,0 +1,411 @@
+import { useAjax } from "@/Hook/useAjax"
+import { configRuleApi } from "@/services/gameData/adlist"
+import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
+import { Badge, Button, Col, Form, Input, InputNumber, Modal, Radio, Row, Select, Space, Switch, message } from "antd"
+import React from "react"
+
+// 监控的数据时间段类型
+export const monitorTimeEnum = [
+    { label: '今日', value: 1 },
+    { label: '过去3天(包括今日)', value: 2 },
+    { label: '过去5天(包括今日)', value: 3 },
+    { label: '过去7天(包括今日)', value: 4 },
+    { label: '过去3天(不包括今日)', value: 5 },
+    { label: '过去5天(不包括今日)', value: 6 },
+    { label: '过去7天(不包括今日)', value: 7 },
+    { label: '广告开始消耗首日(0)', value: 8 },
+    { label: '广告开始消耗(累计3天)', value: 9 },
+    { label: '广告开始消耗(累计5天)', value: 10 },
+    { label: '广告开始消耗(累计7天)', value: 11 }
+]
+export const dataEnum = [
+    { label: '消耗', value: 1 },
+    { label: '预算', value: 2 },
+    { label: '点击率', value: 3 },
+    { label: 'ecpm(千次曝光成本)', value: 4 },
+    { label: '注册', value: 5 },
+    { label: '注册成本(消耗/注册人数)', value: 6 },
+    { label: '付费人数', value: 7 },
+    { label: '付费率(付费人数/注册人数)', value: 8 },
+    { label: '付费成本(消耗/付费人数)', value: 9 },
+    { label: 'arpu(付费金额/注册人数)', value: 10 },
+    { label: 'ROI1', value: 11 },
+    { label: 'ROI2', value: 12 },
+    { label: 'ROI3', value: 13 },
+    { label: 'ROI4', value: 14 },
+    { label: 'ROI5', value: 15 },
+    { label: 'ROI6', value: 16 },
+    { label: 'ROI7', value: 17 },
+    { label: 'LTV1', value: 18 },
+    { label: 'LTV2', value: 19 },
+    { label: 'LTV3', value: 20 },
+    { label: 'LTV4', value: 21 },
+    { label: 'LTV5', value: 22 },
+    { label: 'LTV6', value: 23 },
+    { label: 'LTV7', value: 24 }
+]
+
+export const conditionEnum = [
+    { label: '大于', value: 1 },
+    { label: '大于等于', value: 2 },
+    { label: '等于', value: 3 },
+    { label: '小于等于', value: 4 },
+    { label: '小于', value: 5 }
+]
+
+export const ruleDimensionEnum = [
+    { label: '默认', value: 1 },
+    { label: '渠道', value: 2 },
+    { label: '账号', value: 3 },
+    { label: '广告', value: 4 },
+]
+
+export const executeScopeEnum = [
+    { label: '所有广告(不含删除)', value: 1 },
+    { label: '投放中', value: 2 },
+    { label: '暂停投放', value: 3 },
+    { label: '预算不足', value: 4 },
+]
+
+export const operateTypeEnum = [
+    { label: '仅告警通知', value: 1 },
+    { label: '暂停广告', value: 2 },
+    { label: '启动广告', value: 3 },
+    { label: '增加预算', value: 4 },
+    { label: '减少预算', value: 5 },
+    { label: '广告置顶标黄', value: 6 },
+    { label: '广告置顶标红', value: 7 },
+]
+
+interface Props {
+    gameList: any[]
+    accountList: any[]
+    getChannelChoiceList: any
+    accountLoading?: boolean
+    initialValues?: any
+    visible?: boolean,
+    onClose?: () => void
+    onChange?: () => void
+}
+const ConfigModal: React.FC<Props> = ({ gameList, accountList, getChannelChoiceList, accountLoading, initialValues = {}, visible, onClose, onChange }) => {
+
+    /****************************/
+    const [form] = Form.useForm()
+    const ruleDimension = Form.useWatch('ruleDimension', form)
+
+    const configRule = useAjax((params) => configRuleApi(params))
+    /****************************/
+
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        console.log('===========>', data)
+        if (initialValues?.id) {
+            data.id = initialValues?.id
+        }
+        data.ruleCondition = data.ruleCondition.map((item: any, index: number) => ({ ...item, id: index + 1 }))
+        if (data.ruleDimension === 1) {
+            data.effectiveScope = null
+        }
+        configRule.run(data).then(res => {
+            if (res) {
+                if (initialValues?.id) {
+                    message.success('修改成功')
+                } else {
+                    message.success('新增成功')
+                }
+                onChange?.()
+            }
+        })
+    }
+
+    return <Modal
+        title={`新建规则提醒`}
+        visible={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        bodyStyle={{ height: 600, overflow: 'auto' }}
+        width={700}
+        confirmLoading={configRule.loading}
+    >
+        <Form
+            name="basicStrategy"
+            form={form}
+            layout="vertical"
+            autoComplete="off"
+            colon={false}
+            initialValues={Object.keys(initialValues).length > 0 ? { ...initialValues } : { ruleDimension: 1, isStart: true, ruleType: 1, department: 1, ruleCondition: [{}] }}
+        >
+            <Row gutter={10}>
+                <Col span={24}><h3 style={{ borderBottom: '1px solid #f0f0f0' }}><strong>基本配置</strong></h3></Col>
+                <Col span={12}>
+                    <Form.Item label='规则名称' name='ruleName' rules={[{ required: true, message: '请输入规则名称!' }]}>
+                        <Input placeholder="请输入规则名称" />
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='规则类型' name='ruleType' rules={[{ required: true, message: '请选择规则类型!' }]}>
+                        <Select
+                            showSearch
+                            allowClear
+                            placeholder={'请选择规则类型'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>个人规则</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='部门' name='department' rules={[{ required: true, message: '请选择规则类型!' }]}>
+                        <Select
+                            showSearch
+                            allowClear
+                            placeholder={'请选择规则类型'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>游戏投放</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='游戏' name='gameId' rules={[{ required: true, message: '请选择游戏!' }]}>
+                        <Select
+                            showSearch
+                            allowClear
+                            placeholder={'请选择游戏'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            {gameList?.map((item: any) => <Select.Option value={item.id} key={item.id}>{item.game_name}</Select.Option>)}
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='媒体' name='mediaType' rules={[{ required: true, message: '请选择媒体!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择媒体类型'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>腾讯</Select.Option>
+                            <Select.Option value={2}>头条</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='规则是否启用' name='isStart' rules={[{ required: true, message: '规则是否启用!' }]} valuePropName="checked">
+                        <Switch />
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item
+                        label='规则维度'
+                        name='ruleDimension'
+                        rules={[{ required: true, message: '请选择规则维度!' }]}
+                        help={<Space direction="vertical" size={1}>
+                            <Badge status="default" style={{ fontSize: 12 }} text="告警规则可以配置为游戏对应媒体的默认规则,或者给游戏指定媒体内账号/渠道的所有广告、以及指定的广告配置。" />
+                            <Badge status="default" style={{ fontSize: 12 }} text="告警的优先级为 广告 > 广告账号/渠道 > 媒体。" />
+                            <Badge status="default" style={{ fontSize: 12 }} text="(只会触发同一级别的告警规则,如果在广告上配置了告警规则,那么广告账号的告警规则就不会触发)。" />
+                        </Space>}
+                    >
+                        <Radio.Group onChange={() => form.setFieldsValue({ effectiveScope: undefined })}>
+                            {ruleDimensionEnum.map(item => <Radio value={item.value} key={item.value}>{item.label}</Radio>)}
+                        </Radio.Group>
+                    </Form.Item>
+                </Col>
+                {ruleDimension !== 1 && <Col span={12}><Form.Item label='生效范围' name='effectiveScope' rules={[{ required: true, message: '请选择生效范围!' }]}>
+                    <Select
+                        mode="multiple"
+                        maxTagCount={1}
+                        showSearch
+                        style={{ minWidth: 140 }}
+                        allowClear
+                        placeholder={'请选择生效范围'}
+                        filterOption={(input, option) =>
+                            (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }
+                        loading={accountLoading}
+                    >
+                        {ruleDimension === 2 ?
+                            getChannelChoiceList?.data?.map((item: { id: React.Key | null | undefined; agentName: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined }) => <Select.Option value={item.id} key={item.id}>{item.agentName}</Select.Option>) :
+                            ruleDimension === 3 ? accountList.map(item => <Select.Option key={item.value} value={item.value}>{item.label.toString() + (item.corporationName ? `_${item.corporationName}` : '')}</Select.Option>) :
+                                undefined
+                        }
+                    </Select>
+                </Form.Item></Col>}
+
+
+                <Col span={24}>
+                    <h3 style={{ borderBottom: '1px solid #f0f0f0' }}><strong>告警动作</strong></h3>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='告警方式' name='alarmType' rules={[{ required: true, message: '请选择告警方式!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择告警方式'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>短信</Select.Option>
+                            <Select.Option value={2}>电话</Select.Option>
+                            <Select.Option value={3}>钉钉</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='通知频率' name='alarmFrequency' rules={[{ required: true, message: '请输入通知频率!' }]}>
+                        <InputNumber addonAfter="分钟/次" min={0} />
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='通知总次数' name='alarmCount' rules={[{ required: true, message: '请输入通知总次数!' }]}>
+                        <InputNumber addonAfter="次数" min={0} />
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='操作维度' name='operateDimension' rules={[{ required: true, message: '请选择操作维度!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择操作维度'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>广告</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='操作类型' name='operateType' rules={[{ required: true, message: '请选择操作类型!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择操作类型'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            {operateTypeEnum.map(item => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='执行范围' name='executeScope' rules={[{ required: true, message: '请选择执行范围!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择执行范围'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            {executeScopeEnum.map(item => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={12}>
+                    <Form.Item label='数据巡视周期' name='dataVisitsPeriod' rules={[{ required: true, message: '请选择数据巡视周期!' }]}>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择数据巡视周期'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>实时巡视周期</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Col>
+                <Col span={24}><h3 style={{ borderBottom: '1px solid #f0f0f0' }}><strong>规则条件</strong><span style={{ fontSize: 12 }}>注:最多可同时设置5个指标条件</span></h3></Col>
+                <Col span={24}>
+                    <Form.List name="ruleCondition">
+                        {(fields, { add, remove }) => (<>
+                            {fields.map(({ key, name, ...restField }, index) => (<Space key={key} align="center">
+                                <strong style={{ paddingTop: 6, display: 'inline-block' }}>条件{index + 1}</strong>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'monitorTimeType']}
+                                    rules={[{ required: true, message: '请选择监控的数据时间段' }]}
+                                    label='监控的数据时间段'
+                                    style={{ width: 180 }}
+                                >
+                                    <Select
+                                        allowClear
+                                        placeholder={'请选择执行范围'}
+                                        filterOption={(input, option) =>
+                                            (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                        }
+                                    >
+                                        {monitorTimeEnum.map(item => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                                    </Select>
+                                </Form.Item>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'dataType']}
+                                    rules={[{ required: true, message: '请选择指标类型' }]}
+                                    label='指标类型'
+                                >
+                                    <Select
+                                        allowClear
+                                        placeholder={'请选择指标类型'}
+                                        filterOption={(input, option) =>
+                                            (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                        }
+                                        style={{ width: 140 }}
+                                    >
+                                        {dataEnum.map(item => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                                    </Select>
+                                </Form.Item>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'conditionType']}
+                                    rules={[{ required: true, message: '请选择判断条件' }]}
+                                    label='判断条件'
+                                >
+                                    <Select
+                                        style={{ width: 100 }}
+                                        allowClear
+                                        placeholder={'请选择判断条件'}
+                                        filterOption={(input, option) =>
+                                            (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                        }
+                                    >
+                                        {conditionEnum.map(item => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                                    </Select>
+                                </Form.Item>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'dataNum']}
+                                    rules={[{ required: true, message: '请输入数值' }]}
+                                    label='数值'
+                                >
+                                    <InputNumber min={0} placeholder="请输入数值" style={{ width: 130 }} />
+                                </Form.Item>
+                                {fields.length > 1 && <div style={{ paddingTop: 5, color: 'red' }}><MinusCircleOutlined onClick={() => remove(name)} /></div>}
+                            </Space>))}
+                            <Form.Item noStyle>
+                                <Button type="dashed" disabled={fields.length >= 5} onClick={() => add()} block icon={<PlusOutlined />}>
+                                    新增
+                                </Button>
+                            </Form.Item>
+                        </>)}
+                    </Form.List>
+                </Col>
+            </Row>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(ConfigModal)

+ 32 - 0
src/pages/gameDataStatistics/adlist/strategy/enableConfig.tsx

@@ -0,0 +1,32 @@
+import { useAjax } from "@/Hook/useAjax"
+import { configEnableApi } from "@/services/gameData/adlist"
+import { Switch, message } from "antd"
+import React, { useEffect, useState } from "react"
+
+
+interface Props {
+    value: any
+}
+const EnableConfig: React.FC<Props> = ({ value }) => {
+
+    const configEnable = useAjax((params) => configEnableApi(params))
+    const [checked, setChecked] = useState<boolean>(value.isStart)
+
+    useEffect(() => {
+        setChecked(value.isStart)
+    }, [value.isStart])
+
+    const onChange = (c: boolean) => {
+        const { id } = value
+        configEnable.run({ isStart: c, ids: [id] }).then(res => {
+            if (res) {
+                setChecked(c)
+                message.success(c ? '启用成功' : '暂停成功')
+            }
+        })
+    }
+
+    return <Switch size="small" checked={checked} onChange={onChange} loading={configEnable.loading} />
+}
+
+export default React.memo(EnableConfig)

+ 282 - 0
src/pages/gameDataStatistics/adlist/strategy/index.tsx

@@ -0,0 +1,282 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getAllOfOwnerUserApi, getChannelChoiceListApi, getGameListNewApi, getTtAllUserListApi } from "@/services/gameData"
+import { GetPolicyConfigProps, configEnableApi, configHandleApi, delConfigRuleApi, getPolicyConfigListApi } from "@/services/gameData/adlist"
+import { PlusOutlined } from "@ant-design/icons"
+import { Button, Card, Col, Form, Input, Popconfirm, Row, Select, Space, message } from "antd"
+import React, { useEffect, useState } from "react"
+import ConfigModal from "./configModal"
+import Tables from "@/components/Tables"
+import style from '../../components/TableData/index.less'
+import columnsPos from "./tableConfig"
+import WhiteList from "./whiteList"
+
+
+const Strategy: React.FC = () => {
+
+    /******************************/
+    const [form] = Form.useForm()
+    const [initialValues, setInitialValues] = useState<any>({})
+    const [initialValuesS, setInitialValuesS] = useState<any[]>([])
+    const [queryFrom, setQueryForm] = useState<GetPolicyConfigProps>({ pageNum: 1, pageSize: 20 })
+    const [gameList, setGameList] = useState<{ id: number, game_name: string }[]>([])
+    const [visible, setVisible] = useState<boolean>(false)
+    const [accountList, setAccountList] = useState<any[]>([])
+    const [whiteVisible, setWhiteVisible] = useState<boolean>(false)
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+
+    const getGameList = useAjax((params) => getGameListNewApi(params))
+    const getPolicyConfigList = useAjax((params) => getPolicyConfigListApi(params))
+    const delConfigRule = useAjax((params) => delConfigRuleApi(params))
+    const configHandle = useAjax((params) => configHandleApi(params))
+    const configEnable = useAjax((params) => configEnableApi(params))
+    const getChannelChoiceList = useAjax(() => getChannelChoiceListApi()) // 渠道
+    const getAllOfOwnerUser = useAjax(() => getAllOfOwnerUserApi()) // 账号
+    const getTtAllUserList = useAjax(() => getTtAllUserListApi()) // 账号
+    /******************************/
+
+    useEffect(() => {
+        getPolicyConfigList.run(queryFrom)
+    }, [queryFrom])
+
+    const onFinish = (data: any) => {
+        let oldQueryFrom = JSON.parse(JSON.stringify(queryFrom))
+        setQueryForm({ ...oldQueryFrom, ...data, pageNum: 1 })
+    }
+
+    useEffect(() => {
+        getChannelChoiceList.run()
+        async function getAccount() {
+            let data: any[] = []
+            let res1 = await getAllOfOwnerUser.run()
+            let data1 = []
+            if (res1) {
+                data1 = res1?.map((item: any) => ({ label: item.accountId, value: item.accountId, corporationName: item.corporationName }))
+            }
+            let res2 = await getTtAllUserList.run()
+            let data2 = []
+            if (res2) {
+                data2 = res2?.map((item: any) => ({ label: item.accountId, value: item.accountId, corporationName: item.accountName }))
+            }
+            data = [...data1, ...data2]
+            setAccountList(data)
+        }
+        getAccount()
+    }, [])
+
+    useEffect(() => {
+        getGameList.run({ sourceSystem: 'ZX_ONE' }).then(res => {
+            if (res) {
+                const { gameList } = res
+                setGameList(gameList)
+            }
+        })
+    }, [])
+
+    const edit = (value: any, isCopy?: boolean) => {
+        let data = JSON.parse(JSON.stringify(value))
+        if (isCopy) {
+            delete data?.id
+            data.ruleName = data.ruleName + '_Copy'
+        }
+        setInitialValues(data)
+        setVisible(true)
+    }
+
+    const del = (value: string) => {
+        delConfigRule.run(value).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getPolicyConfigList.refresh()
+            }
+        })
+    }
+
+    const handle = (value: any[], type: string) => {
+        switch (type) {
+            case 'zt': case 'qd':
+                configEnable.run({ ids: value.map(item => item.id), isStart: type === 'zt' ? false : true }).then(res => {
+                    if (res) {
+                        message.success(type === 'zt' ? '暂停成功' : '启用成功')
+                        getPolicyConfigList.refresh()
+                        setSelectedRows([])
+                    }
+                })
+                break
+            case 'bmd':
+                setInitialValuesS(value)
+                setWhiteVisible(true)
+                break
+        }
+    }
+
+    return <Card
+        style={{ borderRadius: 8 }}
+        headStyle={{ textAlign: 'left' }}
+        bodyStyle={{ padding: '5px 10px' }}
+    >
+        <div style={{ textAlign: 'center', fontWeight: 'bold', padding: '4px 6px 6px', fontSize: 16, marginBottom: 4, position: 'relative' }}>
+            游戏广告策略
+        </div>
+        <Space style={{ width: '100%' }} direction="vertical" size={10}>
+            <Form layout="inline" className='queryForm' initialValues={initialValues} name="basicStrategy" form={form} onFinish={onFinish}>
+                <Row gutter={[0, 6]}>
+                    <Col><Form.Item name='isStart'>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择启用状态'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={false}>未启用</Select.Option>
+                            <Select.Option value={true}>已启用</Select.Option>
+                        </Select>
+                    </Form.Item></Col>
+                    <Col><Form.Item name='gameId'>
+                        <Select
+                            maxTagCount={1}
+                            showSearch
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择游戏'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            {gameList?.map((item: any) => <Select.Option value={item.id} key={item.id}>{item.game_name}</Select.Option>)}
+                        </Select>
+                    </Form.Item></Col>
+                    <Col><Form.Item name='mediaType'>
+                        <Select
+                            style={{ minWidth: 140 }}
+                            allowClear
+                            placeholder={'请选择媒体类型'}
+                            filterOption={(input, option) =>
+                                (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }
+                        >
+                            <Select.Option value={1}>腾讯</Select.Option>
+                            <Select.Option value={2}>头条</Select.Option>
+                        </Select>
+                    </Form.Item></Col>
+                    <Col><Form.Item name='ruleName'>
+                        <Input placeholder="规则名称" />
+                    </Form.Item></Col>
+                    <Col>
+                        <Space>
+                            <Button type="primary" htmlType="submit">搜索</Button>
+                            <Button onClick={() => form.resetFields()}>重置</Button>
+                            <Button icon={<PlusOutlined />} type="primary" onClick={() => { setVisible(true); setInitialValues({}) }}>添加规则条件</Button>
+                        </Space>
+                    </Col>
+                </Row>
+            </Form>
+
+            <Space>
+                <Button type="primary" loading={configEnable.loading} disabled={selectedRows?.length === 0} onClick={() => handle(selectedRows, 'qd')}>批量启用</Button>
+                <Button loading={configEnable.loading} disabled={selectedRows?.length === 0} onClick={() => handle(selectedRows, 'zt')}>批量暂停</Button>
+                <Button type="primary" disabled={selectedRows?.length === 0} onClick={() => handle(selectedRows, 'bmd')}>批量添加白名单</Button>
+                <Popconfirm
+                    title="确定删除?"
+                    onConfirm={() => { del(selectedRows.map(item => item.id).toString()) }}
+                >
+                    <Button type="primary" loading={delConfigRule.loading} disabled={selectedRows?.length === 0} danger>批量删除</Button>
+                </Popconfirm>
+            </Space>
+
+            <div className={`${style['small']}`}>
+                <Tables
+                    className={`all_table content_table_body`}
+                    bordered
+                    sortDirections={['ascend', 'descend', null]}
+                    current={queryFrom.pageNum}
+                    pageSize={queryFrom.pageSize}
+                    columns={columnsPos(accountList, getChannelChoiceList, edit, del, handle)}
+                    dataSource={getPolicyConfigList?.data?.records}
+                    scroll={{ x: 1000, y: 600 }}
+                    onChange={(pagination: any, filters: any, sortData: any) => {
+                        let { current, pageSize } = pagination
+                        let newQueryForm = JSON.parse(JSON.stringify(queryFrom))
+                        if (sortData && sortData?.order) {
+                            newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'asc' : 'desc'
+                            newQueryForm['sortFiled'] = sortData?.field
+                        } else {
+                            delete newQueryForm['sortType']
+                            delete newQueryForm['sortFiled']
+                        }
+                        newQueryForm.pageNum = current
+                        newQueryForm.pageSize = pageSize
+                        setQueryForm({ ...newQueryForm })
+                    }}
+                    size="small"
+                    total={getPolicyConfigList?.data?.total}
+                    loading={getPolicyConfigList?.loading}
+                    defaultPageSize={20}
+                    rowSelection={{
+                        selectedRowKeys: selectedRows.map(item => item.id + ''),
+                        onSelect: (record: { id: number }, selected: boolean) => {
+                            if (selected) {
+                                selectedRows.push({ ...record })
+                                setSelectedRows([...selectedRows])
+                            } else {
+                                let newSelectAccData = selectedRows.filter((item: { id: number }) => item.id !== record.id)
+                                setSelectedRows([...newSelectAccData])
+                            }
+                        },
+                        onSelectAll: (selected: boolean, selectedRowss: { id: number }[], changeRows: { id: number }[]) => {
+                            if (selected) {
+                                let newSelectAccData = [...selectedRows]
+                                changeRows.forEach((item: { id: number }) => {
+                                    let index = newSelectAccData.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index === -1) {
+                                        newSelectAccData.push({ ...item })
+                                    }
+                                })
+                                setSelectedRows([...newSelectAccData])
+                            } else {
+                                let newSelectAccData = selectedRows.filter((item: { id: number }) => {
+                                    let index = changeRows.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index !== -1) {
+                                        return false
+                                    } else {
+                                        return true
+                                    }
+                                })
+                                setSelectedRows([...newSelectAccData])
+                            }
+                        }
+                    }}
+                />
+            </div>
+        </Space>
+
+
+        {visible && <ConfigModal
+            visible={visible}
+            onClose={() => setVisible(false)}
+            accountList={accountList}
+            getChannelChoiceList={getChannelChoiceList}
+            accountLoading={getChannelChoiceList.loading || getAllOfOwnerUser.loading || getTtAllUserList.loading}
+            gameList={gameList}
+            initialValues={initialValues}
+            onChange={() => {
+                setVisible(false)
+                getPolicyConfigList.refresh()
+            }}
+        />}
+
+        {/* 白名单 */}
+        {whiteVisible && <WhiteList
+            visible={whiteVisible}
+            onClose={() => setWhiteVisible(false)}
+            onChange={() => {
+                setWhiteVisible(false)
+                getPolicyConfigList.refresh()
+            }}
+            initialValues={initialValuesS}
+        />}
+    </Card>
+}
+
+export default Strategy

+ 216 - 0
src/pages/gameDataStatistics/adlist/strategy/tableConfig.tsx

@@ -0,0 +1,216 @@
+
+
+import { Row, Col, Popconfirm, Badge } from "antd"
+import WidthEllipsis from "@/components/widthEllipsis"
+import React from "react"
+import { conditionEnum, dataEnum, executeScopeEnum, monitorTimeEnum, operateTypeEnum, ruleDimensionEnum } from "./configModal"
+import EnableConfig from "./enableConfig"
+
+function columnsPos(
+    accountList: any[],
+    getChannelChoiceList: any,
+    edit: (value: any, isCopy?: boolean) => void,
+    del: (value: any) => void,
+    handle: (value: any, type: string) => void,
+) {
+
+    let newArr: any = [
+        {
+            title: '规则名称',
+            dataIndex: 'ruleName',
+            key: 'ruleName',
+            align: 'center',
+            width: 100,
+            fixed: true,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '规则类型',
+            dataIndex: 'ruleType',
+            key: 'ruleType',
+            align: 'center',
+            width: 90,
+            fixed: true,
+            render: (a: number, b: any) => {
+                return {1: '个人规则'}[1]
+            }
+        },
+        {
+            title: '部门',
+            dataIndex: 'department',
+            key: 'department',
+            align: 'center',
+            width: 100,
+            render: (a: number, b: any) => {
+                return { 1: '游戏投放' }[a]
+            }
+        },
+        {
+            title: '游戏',
+            dataIndex: 'gameName',
+            key: 'gameName',
+            align: 'center',
+            width: 100,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '媒体',
+            dataIndex: 'mediaType',
+            key: 'mediaType',
+            align: 'center',
+            width: 100,
+            render: (a: number) => {
+                return { 1: '腾讯', 2: '头条' }[a]
+            }
+        },
+        {
+            title: '规则维度',
+            dataIndex: 'ruleDimension',
+            key: 'ruleDimension',
+            width: 90,
+            align: 'center',
+            render: (a: number) => {
+                return ruleDimensionEnum.find(item => item.value === a)?.label
+            }
+        },
+        {
+            title: '生效范围',
+            dataIndex: 'effectiveScope',
+            key: 'effectiveScope',
+            width: 250,
+            render: (a: number[], b: any) => {
+                return <WidthEllipsis value={a.map(item => {
+                    if (b?.ruleDimension === 2) {
+                        return getChannelChoiceList?.data?.find((i: { id: number }) => i.id === item)?.agentName
+                    } else if (b?.ruleDimension === 3) {
+                        let data = accountList?.find(i => i.value === item)
+                        return data.label.toString() + (data.corporationName ? `_${data.corporationName}` : '')
+                    }
+                    return undefined
+                }).toString()} />
+            }
+        },
+        {
+            title: '账号白名单',
+            dataIndex: 'accountWhitelist',
+            key: 'accountWhitelist',
+            align: 'center',
+            width: 120,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '广告白名单',
+            dataIndex: 'adWhitelist',
+            key: 'adWhitelist',
+            align: 'center',
+            width: 120,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '执行范围',
+            dataIndex: 'executeScope',
+            key: 'executeScope',
+            align: 'center',
+            width: 100,
+            render: (a: number, b: any) => {
+                return executeScopeEnum.find(item => item.value === a)?.label
+            }
+        },
+        {
+            title: '告警动作类型',
+            dataIndex: 'operateType',
+            key: 'operateType',
+            align: 'center',
+            width: 100,
+            render: (a: number, b: any) => {
+                return operateTypeEnum.find(item => item.value === a)?.label
+            }
+        },
+        {
+            title: '规则条件',
+            dataIndex: 'ruleCondition',
+            key: 'ruleCondition',
+            width: 300,
+            render: (a: any[], b: any) => {
+                return <WidthEllipsis value={a.map(item => {
+                    let monitorTimeName = monitorTimeEnum.find(i => i.value === item.monitorTimeType)?.label
+                    let dataName = dataEnum.find(i => i.value === item.dataType)?.label
+                    let conditionName = conditionEnum.find(i => i.value === item.conditionType)?.label
+                    return `${monitorTimeName}-${dataName}-${conditionName}-${item.dataNum}`
+                }).toString()} />
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'createByName',
+            key: 'createByName',
+            align: 'center',
+            width: 80,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 135,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '更新人',
+            dataIndex: 'updateByName',
+            key: 'updateByName',
+            align: 'center',
+            width: 80,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '更新时间',
+            dataIndex: 'updateTime',
+            key: 'updateTime',
+            align: 'center',
+            width: 135,
+            render: (a: string, b: any) => (<WidthEllipsis value={a} />)
+        },
+        {
+            title: '规则启用状态',
+            dataIndex: 'isStart',
+            key: 'isStart',
+            align: 'center',
+            width: 60,
+            fixed: 'right',
+            render: (a: boolean, b: any) => {
+                return <EnableConfig value={b} />
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            width: 150,
+            fixed: 'right',
+            render: (a: string, b: any) => (
+                <Row justify='center' gutter={[10, 0]}>
+                    <Col><a style={{ fontSize: "12px" }} onClick={() => { edit(b) }}>修改</a></Col>
+                    {/* <Col><a style={{ fontSize: "12px" }} onClick={() => { edit(b, true) }}>复制</a></Col> */}
+                    <Col><a style={{ fontSize: "12px" }} onClick={() => { handle([b], 'bmd') }}>添加白名单</a></Col>
+                    <Col>
+                        <Popconfirm
+                            title="确定删除?"
+                            onConfirm={() => { del(b.id) }}
+                            okText="是"
+                            cancelText="否"
+                        >
+                            <a style={{ fontSize: "12px", color: 'red' }}>删除</a>
+                        </Popconfirm>
+                    </Col>
+                </Row>
+            )
+        }
+    ]
+    return newArr
+}
+
+
+export default columnsPos

+ 88 - 0
src/pages/gameDataStatistics/adlist/strategy/whiteList.tsx

@@ -0,0 +1,88 @@
+import { useAjax } from "@/Hook/useAjax"
+import { configHandleApi } from "@/services/gameData/adlist"
+import { Form, Input, Modal, Radio, Select, message } from "antd"
+import React from "react"
+
+const validTimeEnum = [
+    { label: '当日有效(自然日)', value: 1 },
+    { label: '长期有效', value: 2 }
+]
+
+interface Props {
+    initialValues?: any[]
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+const WhiteList: React.FC<Props> = ({ initialValues = [], visible, onClose, onChange }) => {
+
+    /****************************/
+    const [form] = Form.useForm()
+    const configHandle = useAjax((params) => configHandleApi(params))
+    /****************************/
+
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        let regex = /[^,,]+/g;
+        data.accountWhitelist = data.accountWhitelist.replace(/\s/g, "").match(regex)
+        data.adWhitelist = data.adWhitelist.replace(/\s/g, "").match(regex)
+        data.ids = initialValues.map(item => item.id)
+        configHandle.run(data).then(res => {
+            if (res) {
+                message.success('修改成功')
+                onChange?.()
+            }
+        })
+    }
+
+    return <Modal
+        title={`${initialValues.map(item => item.ruleName).toString()} 添加规则白名单`}
+        visible={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        confirmLoading={configHandle.loading}
+    >
+        <Form
+            name="basicWhiteList"
+            form={form}
+            layout="vertical"
+            autoComplete="off"
+            colon={false}
+            initialValues={initialValues.length === 1 ? { ...initialValues[0] } : {}}
+        >
+            <Form.Item label='账号白名单' name='accountWhitelist'>
+                <Input.TextArea placeholder="请输入账号白名单(多个,隔开,a,b,c)" />
+            </Form.Item>
+            <Form.Item label='账号有效时长' name='accountValidTime'>
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder={'请选择账号有效时长'}
+                    filterOption={(input, option) =>
+                        (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                >
+                    {validTimeEnum?.map((item: any) => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                </Select>
+            </Form.Item>
+            <Form.Item label='广告白名单' name='adWhitelist'>
+                <Input.TextArea placeholder="请输入广告白名单(多个,隔开,a,b,c)" />
+            </Form.Item>
+            <Form.Item label='广告有效时长' name='adValidTime'>
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder={'请选择广告有效时长'}
+                    filterOption={(input, option) =>
+                        (option?.children as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                >
+                    {validTimeEnum?.map((item: any) => <Select.Option value={item.value} key={item.value}>{item.label}</Select.Option>)}
+                </Select>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(WhiteList)

+ 4 - 2
src/pages/gameDataStatistics/roleOperate/strategy/strategyModal.tsx

@@ -239,7 +239,8 @@ const StrategyModal: React.FC<Props> = ({ superGameList, initialValues, visible,
                 </Space>
             </Form.Item>
 
-            {type !== 3 && <Form.Item
+            {/* {type !== 3 &&  */}
+            <Form.Item
                 label="时间(小时)"
             >
                 <Space>
@@ -273,7 +274,8 @@ const StrategyModal: React.FC<Props> = ({ superGameList, initialValues, visible,
                         </Select>
                     </Form.Item>}
                 </Space>
-            </Form.Item>}
+            </Form.Item>
+            {/* } */}
             <Form.Item label="策略备注" name='configExplain' rules={[{ required: true, message: '请选择策略备注!' }]}>
                 {/* <Input.TextArea placeholder="配置说明:请写出具体的配置细节。如:首次充值金额大于等于500并且注册时间24小时内的用户,发送钉钉消息" /> */}
                 <Input.TextArea placeholder="请输入备注" />

+ 138 - 0
src/services/gameData/adlist.ts

@@ -2,6 +2,7 @@ import { request } from 'umi';
 import { api, erpApi } from '../api';
 import { Paging, SortProps } from './rankingList';
 let wapi = api + '/gameData'
+let mapi = api + '/manage'
 
 export interface PromotionDataDay extends Paging, SortProps {
     accountId?: number,   // 推广账号ID
@@ -171,4 +172,141 @@ export async function newEditAdqAdgroupsDataApi(data: EditAdqAdgroupsProps) {
         method: 'POST',
         data
     });
+}
+
+
+/************************游戏广告策略*****************************/
+export interface GetPolicyConfigProps extends Paging {
+    gameId?: number,    // 子游戏维度
+    isStart?: boolean   /// 是否启用
+    mediaType?: number  // 媒体类型 1-腾讯;2-头条
+    ruleName?: string
+}
+
+/**
+ * 规则列表
+ * @param data 
+ * @returns 
+ */
+export async function getPolicyConfigListApi(data: EditAdqAdgroupsProps) {
+    return request(mapi + `/ad/policy/config/list`, {
+        method: 'POST',
+        data
+    });
+}
+
+export interface ConfigRuleProps {
+    // 规则下的账号白名单有效时间类型:1-当日有效(自然日);2-长期有效
+    accountValidTime: number,
+    // 规则下的账号白名单
+    accountWhitelist: number[],
+    // 规则下的广告白名单有效时间类型:1-当日有效(自然日);2-长期有效
+    adValidTime: number,
+    // 规则下的广告白名单
+    adWhitelist: number[],
+    // 通知总次数
+    alarmCount: number
+    // 通知频率:单位:分钟/次
+    alarmFrequency: number
+    // 告警方式:1-短信;2-电话;3-钉钉
+    alarmType: number
+    // 数据数据巡视周期:1-实时巡视周期
+    dataVisitsPeriod: number
+    // 部门类型:1-游戏投放
+    department: number
+    // 执行范围:1-所有广告(不含删除);2-投放中;3-暂停投放;4-预算不足
+    executeScope: number
+    // 游戏ID(子游戏维度)
+    gameId: number
+    // 是否启用
+    isStart: boolean
+    // 媒体类型:1-腾讯;2-头条
+    mediaType: number
+    // 操作维度:1-广告
+    operateDimension: number
+    // 操作类型:1-仅告警通知;2-暂停广告;3-启动广告;4-增加预算;5-减少预算;6-广告置顶标黄;7-广告置顶标红
+    operateType: number
+
+    // 规则条件(最多可同事设置5个指标条件)
+    ruleCondition: {
+        // 1-大于;2-大于等于;3-等于;4-小于等于;5-小于
+        conditionType: number
+        // 具体数值
+        dataNum: number
+        /**
+         * 指标类型:1-消耗;2-预算;3-点击率;4-ecpm(千次曝光成本);5-注册;6-注册成本(消耗/注册人数);7-付费人数;8-付费率(付费人数/注册人数)
+            9-付费成本(消耗/付费人数);10-arpu(付费金额/注册人数);11-ROI1(首日付费金额/消耗);12-ROI2(?/消耗);13-ROI3;
+            14-ROI4;15-ROI5;16-ROI6;17-ROI7;18-LTV1;19-LTV2;20-LTV3;21-LTV4;22-LTV5;23-LTV6;24-LTV7;
+         */
+        dataType: number
+        /**
+         * 监控的数据时间段类型:1-今日;2-过去3天(包括今日);3-过去5天(包括今日);4-过去7天(包括今日);
+        5-过去3天(不包括今日);6-过去5天(不包括今日);7-过去7天(不包括今日);
+        8-广告开始消耗首日(0);9-广告开始消耗(累计3天);10-广告开始消耗(累计5天);11-广告开始消耗(累计7天);
+         */
+        monitorTimeType: number
+        id?: number
+    }
+    // 规则维度:1-默认;2-渠道;3-账号;4-广告
+    ruleDimension: number
+    ruleName: string
+    // 规则类型:1-个人规则
+    ruleType: number
+    // 生效范围(依据规则维度变化):0(默认维度:当前创建人的所有广告);其他(对应规则维度的ID集合)
+    effectiveScope?: number[]
+    id?: number
+}
+
+/**
+ * 新增修改
+ * @param data 
+ * @returns 
+ */
+export async function configRuleApi(data: ConfigRuleProps) {
+    return request(mapi + `/ad/policy/config/add/or/update`, {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 删除策略
+ * @param ids 
+ * @returns 
+ */
+export async function delConfigRuleApi(ids: string) {
+    return request(mapi + `/ad/policy/config/delete/${ids}`, {
+        method: 'DELETE'
+    });
+}
+
+export interface ConfigHandleProps {
+    accountValidTime: number,
+    accountWhitelist: number[],
+    adValidTime: number,
+    adWhitelist: number[],
+    ids: number,
+}
+/**
+ * 游戏广告策略配置批量操作:添加白名单;启用;暂停
+ * @param ids 
+ * @returns 
+ */
+export async function configHandleApi(data: ConfigHandleProps) {
+    return request(mapi + `/ad/policy/config/batch/operation`, {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 启用 暂停
+ * @param data 
+ * @returns 
+ */
+export async function configEnableApi(data: { ids: number[], isStart: boolean }) {
+    return request(mapi + `/ad/policy/config/batch/start`, {
+        method: 'POST',
+        data
+    });
 }