wjx 5 months ago
parent
commit
39603d9dfa

+ 1 - 14
config/routerConfig.ts

@@ -106,20 +106,7 @@ const launchSystemV3 = {
             path: '/launchSystemV3/account',
             path: '/launchSystemV3/account',
             name: '广告账户管理',
             name: '广告账户管理',
             access: 'account',
             access: 'account',
-            routes: [
-                {
-                    name: '小说',
-                    path: '/launchSystemV3/account/novel',
-                    access: 'novel',
-                    component: './launchSystemNew/account/novel',
-                },
-                {
-                    name: '游戏',
-                    path: '/launchSystemV3/account/game',
-                    access: 'game',
-                    component: './launchSystemNew/account/game',
-                },
-            ],
+            component: './launchSystemV3/account',
         },
         },
         {
         {
             path: '/launchSystemV3/tencentAdPutIn',
             path: '/launchSystemV3/tencentAdPutIn',

+ 14 - 1
src/pages/launchSystemNew/account/novel/index.tsx

@@ -27,7 +27,7 @@ const AdAuthorize: React.FC = () => {
     /*************************/
     /*************************/
     const { groupListInit } = useModel('useLaunchAdq.useAdAuthorize')
     const { groupListInit } = useModel('useLaunchAdq.useAdAuthorize')
     const userInfo = useModel('@@initialState', model => model.initialState?.currentUser)
     const userInfo = useModel('@@initialState', model => model.initialState?.currentUser)
-    const [queryForm, setQueryForm] = useState<GetAdAccountParams>({ pageNum: 1, pageSize: 20 })
+    const [queryForm, setQueryForm] = useState<GetAdAccountParams>({ pageNum: 1, pageSize: 20, appType: 1 })
     const [remarkData, set_remarkData] = useState<{ visible: boolean, remark: string, data: any }>({
     const [remarkData, set_remarkData] = useState<{ visible: boolean, remark: string, data: any }>({
         visible: false,
         visible: false,
         remark: '',
         remark: '',
@@ -196,6 +196,19 @@ const AdAuthorize: React.FC = () => {
                     size="small"
                     size="small"
                     scroll={{ y: 600 }}
                     scroll={{ y: 600 }}
                     leftChild={<Space wrap>
                     leftChild={<Space wrap>
+                        <Select
+                            style={{ width: 90 }}
+                            placeholder="选择应用类型"
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, appType: e })
+                            }}
+                            value={queryForm.appType}
+                            options={[
+                                { label: '小说', value: 1 },
+                                { label: '游戏', value: 2 },
+                                { label: '短剧', value: 3 }
+                            ]}
+                        />
                         <Select
                         <Select
                             style={{ width: 120 }}
                             style={{ width: 120 }}
                             placeholder="选择操作类型"
                             placeholder="选择操作类型"

+ 104 - 0
src/pages/launchSystemV3/account/addAccountToGroup.tsx

@@ -0,0 +1,104 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getAllUserAccountApi } from "@/services/launchAdq/adAuthorize"
+import { addNewAccountToGroupApi, getAccountListApi } from "@/services/launchAdq/subgroup"
+import { PlusOutlined } from "@ant-design/icons"
+import { Button, Form, message, Modal, Select, Transfer } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+
+
+interface Props {
+    onChange?: () => void
+}
+/**
+ * 新增至分组
+ * @param props 
+ * @returns 
+ */
+const AddAccountToGroup: React.FC<Props> = (props) => {
+
+    /*********************************/
+    const { onChange } = props
+    const [form] = Form.useForm();
+    let currGroupId = Form.useWatch('currGroupId', form)
+    const [visible, setVisible] = useState<boolean>(false)
+    const [mockData, setMockData] = useState<{ key: string, title: string }[]>([])
+    const { getGroupList } = useModel('useLaunchAdq.useAdAuthorize')
+
+    const getAccountList = useAjax((params) => getAccountListApi(params))
+    const addNewAccountToGroup = useAjax((params) => addNewAccountToGroupApi(params))
+    const getAllUserAccount = useAjax(() => getAllUserAccountApi())
+    /*********************************/
+
+    useEffect(() => {
+        if (currGroupId) {
+            getAccountList.run(currGroupId).then(res => {
+                console.log(res);
+                form.setFieldsValue({ accountIds: res?.adAccountList?.map((item: { accountId: string }) => item.accountId.toString()) || [] })
+            })
+        }
+    }, [currGroupId])
+
+    const addAccountToGroupHandle = () => {
+        setVisible(true)
+        getAllUserAccount.run().then(res => {
+            setMockData(res?.map((item: { accountId: string, remark: string }) => ({ key: item.accountId.toString(), title: `${item.accountId}${item.remark ? '_' + item.remark : ''}` })))
+        })
+    }
+
+    const handleOk = () => {
+        form.validateFields().then(values => {
+            addNewAccountToGroup.run(values).then(res => {
+                message.success('修改成功')
+                closeHandle()
+                onChange?.()
+            })
+        })
+    }
+
+    const closeHandle = () => {
+        setVisible(false)
+        form.setFieldsValue({ currGroupId: undefined, accountIds: undefined })
+    }
+
+    return <>
+        <Button onClick={addAccountToGroupHandle} type='primary' icon={<PlusOutlined />}>修改分组中账户</Button>
+        {visible && <Modal
+            title='修改分组中账户'
+            open={visible}
+            onOk={handleOk}
+            onCancel={closeHandle}
+            confirmLoading={addNewAccountToGroup.loading}
+        >
+            <Form
+                form={form}
+                labelCol={{ span: 4 }}
+                colon={false}
+                initialValues={{}}
+            >
+                <Form.Item label={<strong>分组</strong>} name='currGroupId' rules={[{ required: true, message: '请选择分组' }]}>
+                    <Select placeholder='请选择分组'>
+                        {getGroupList?.data?.map((item: { groupName: string, groupId: number }) => <Select.Option value={item.groupId} key={item.groupId}>{item.groupName}</Select.Option>)}
+                    </Select>
+                </Form.Item>
+                {currGroupId && <Form.Item label={<strong>账号</strong>} name='accountIds' valuePropName='targetKeys' rules={[{ required: true, message: '请选择账号' }]}>
+                    <Transfer
+                        dataSource={mockData}
+                        showSearch
+                        titles={['未选', '已选']}
+                        oneWay
+                        filterOption={(input: any, option: any) => {
+                            let newInput: string[] = input ? input?.split(/[,,\n\s]+/ig).filter((item: any) => item) : []
+                            return newInput?.some(val => option!.title?.toString().toLowerCase()?.includes(val))
+                        }}
+                        listStyle={{ minHeight: 320 }}
+                        render={item => item.title}
+                    />
+                </Form.Item>}
+            </Form>
+        </Modal>}
+    </>
+}
+
+
+export default React.memo(AddAccountToGroup)

+ 93 - 0
src/pages/launchSystemV3/account/appointPut.tsx

@@ -0,0 +1,93 @@
+import { useAjax } from "@/Hook/useAjax"
+import { addAccountUserApi, cutPutApi } from "@/services/operating/accountyyb"
+import { Form, message, Modal, Select } from "antd"
+import React, { useEffect, useState } from "react"
+
+
+interface Props {
+    allOfMember: any
+    onChange?: () => void
+    onClose?: () => void
+    visible?: boolean,
+    value?: any[]
+}
+
+/**
+ * 指派
+ * @param props 
+ * @returns 
+ */
+const AppointPut: React.FC<Props> = (props) => {
+    /*******************************/
+    const { onChange, onClose, visible, allOfMember, value = [] } = props
+    const [userAll, setUserAll] = useState([])
+
+    const [form] = Form.useForm();
+    const addAccountUser = useAjax((params) => addAccountUserApi(params))
+    /*******************************/
+
+    useEffect(() => {
+        if (value.length === 1) {
+            console.log('value--->', value[0].accountUsers?.map((item: any) => item.putUserId));
+        
+            form.setFieldsValue({ putUserIds: value[0].accountUsers?.map((item: any) => item.putUserId) })
+        }
+    }, [value])
+
+    /** 获取组员 */
+    useEffect(() => {
+        (async function () {
+            let res = allOfMember?.data || await allOfMember.run()
+            if (res?.data) {
+                let useAll: any = []
+                res?.data?.forEach((item: any) => {
+                    let obj = {
+                        key: item.userId,
+                        label: item.nickname
+                    }
+                    useAll.push(obj)
+                })
+                setUserAll(useAll)
+            }
+        })()
+    }, [])
+
+    const handleOk = () => {
+        form.validateFields().then(values => {
+            addAccountUser.run({ ...values, accountIds: value.map((item: { accountId: number }) => item.accountId) }).then(res => {
+                message.success('指派成功')
+                onChange?.()
+            })
+        })
+    }
+
+    return <Modal
+        title="指派投放助理"
+        open={visible}
+        onOk={handleOk}
+        onCancel={() => onClose?.()}
+        confirmLoading={addAccountUser.loading}
+    >
+        <Form
+            form={form}
+            labelCol={{ span: 4 }}
+            colon={false}
+            initialValues={{}}
+        >
+            <Form.Item label={<strong>组员</strong>} name='putUserIds' rules={[{ required: true, message: '请选择分组' }]}>
+                <Select
+                    placeholder='请选择组员'
+                    showSearch
+                    mode="multiple"
+                    filterOption={(input: string, option: any) => {
+                        return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }}
+                >
+                    {userAll?.map((item: { key: string, label: number }) => <Select.Option value={item.key} key={item.key}>{item.label}</Select.Option>)}
+                </Select>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(AppointPut)

+ 127 - 0
src/pages/launchSystemV3/account/changeRecord.tsx

@@ -0,0 +1,127 @@
+import Tables from "@/components/Tables";
+import { DatePicker, Drawer, Form, Input, Modal, Select } from "antd";
+import moment from "moment";
+import React, { useCallback, useEffect, useState } from "react";
+import { useModel } from "umi";
+import { columnsCrGdt } from "./tablesConfig"
+
+type Props = {
+    data?: { name: number, id: number } | null,
+    visible?: boolean,
+    onChange?: () => void,
+    onClose?: () => void
+}
+function ChangeRecord(props: Props) {
+
+    const { data, visible, onClose } = props
+    const { getGdtChangeRecord, editAccountChangeRecord, editGdtChangeRecord, getResource } = useModel('useOperating.useAccountyyb')
+    const [accountQueryForm, setAccountQueryForm] = useState<{ quickAppAccountId: number, pageNum: number, pageSize: number }>({ quickAppAccountId: data?.id as number, pageNum: 1, pageSize: 20 })
+    const [gdtQueryForm, setGdtQueryForm] = useState<{ gdtAccountId: number, pageNum: number, pageSize: number }>({ gdtAccountId: data?.name as number, pageNum: 1, pageSize: 20 })
+    const [editTitle, setEditTitle] = useState<string>('编辑')
+    const [form] = Form.useForm()
+    const [editData, setEditData] = useState<{ recordId: number, putResourceId: number | undefined, resourceNames: string, beginTime: any, endTime: any } | null>(null)
+    const [editVisible, setEditVisible] = useState<boolean>(false)
+
+    useEffect(() => {
+        getList()
+    }, [accountQueryForm, gdtQueryForm])
+
+    const getList = () => {
+        getGdtChangeRecord.run(gdtQueryForm)
+    }
+
+    // 分页
+    const pageChange = useCallback((page: string | number, pageSize?: string | number) => {
+        setGdtQueryForm({ ...gdtQueryForm, pageNum: page as number, pageSize: pageSize as number })
+    }, [accountQueryForm, gdtQueryForm])
+
+    // 编辑
+    const editHandle = useCallback((data: any) => {
+        const { beginTime, endTime, id, putResourceName } = data
+        setEditData({ beginTime: moment(beginTime), endTime: endTime ? moment(endTime) : null, recordId: id, putResourceId: undefined, resourceNames: putResourceName })
+        setEditTitle('编辑 ' + putResourceName)
+        setEditVisible(true)
+        setTimeout(() => {
+            form.resetFields()
+        }, 100)
+    }, [editData, editTitle, editVisible, form])
+
+    // 搜索资源
+    const handleSearch = (value: string) => {
+        getResource.run({ resourceName: value, resourceType: 0 })
+    }
+
+    const handleOk = async () => {
+        form.submit()
+        let { resourceNames, ...data1 }  = await form.validateFields()
+        let proms = { ...data1, beginTime: moment(data1.beginTime).format('YYYY-MM-DD') }
+        if (data1.endTime) {
+            proms.endTime = moment(data1.endTime).format('YYYY-MM-DD')
+        }
+        editGdtChangeRecord.run({ ...proms, recordId: editData?.recordId }).then(res => {
+            if(res) {
+                setEditData(null);
+                setEditVisible(false);
+                getGdtChangeRecord.refresh()
+            }
+        })
+    }
+
+    return <div>
+
+        <Drawer title={data?.name + ' 变更记录'} placement="right" width={1200} onClose={() => { onClose && onClose() }} open={visible}>
+            <Tables
+                columns={columnsCrGdt(editHandle)}
+                dataSource={getGdtChangeRecord?.data?.records}
+                total={getGdtChangeRecord?.data?.total}
+                loading={getGdtChangeRecord?.loading}
+                pageChange={pageChange}
+                sizeChange={pageChange}
+                current={getGdtChangeRecord?.data?.current}
+                pageSize={getGdtChangeRecord?.data?.size}
+                size={'small'}
+                scroll={{ x: 1100 }}
+            />
+        </Drawer>
+
+        {editVisible && <Modal title={editTitle} open={editVisible} onOk={handleOk} onCancel={() => { setEditData(null); form.resetFields(); setEditVisible(false); }} confirmLoading={editAccountChangeRecord.loading || editGdtChangeRecord.loading}>
+            <Form
+                name="basic"
+                form={form}
+                labelCol={{ span: 5 }}
+                wrapperCol={{ span: 19 }}
+                autoComplete="off"
+                initialValues={{ ...editData }}
+            >
+                <Form.Item label="开始时间" name="beginTime" rules={[{ type: 'object' as const, required: true, message: '请选择时间!' }]}>
+                    <DatePicker />
+                </Form.Item>
+                <Form.Item label="结束时间" name="endTime">
+                    <DatePicker />
+                </Form.Item>
+                <Form.Item label="当前投放信息" name="resourceNames">
+                    <Input.TextArea autoSize disabled />
+                </Form.Item>
+                <Form.Item label="投放信息" name="putResourceId" rules={[{ type: 'number', required: true, message: '请选择投放信息!' }]}>
+                    <Select
+                        showSearch
+                        placeholder="选择投放信息"
+                        defaultActiveFirstOption={false}
+                        showArrow={false}
+                        filterOption={false}
+                        onSearch={handleSearch}
+                        notFoundContent={null}
+                    >
+                        {getResource?.data?.map((item: { id: number, resourceName: string }) => <Select.Option key={item.id} value={item.id}>{item.resourceName}</Select.Option>)}
+                    </Select>
+                </Form.Item>
+
+            </Form>
+
+        </Modal>}
+
+    </div>
+}
+
+
+export default React.memo(ChangeRecord)

+ 69 - 0
src/pages/launchSystemV3/account/checkAccount.tsx

@@ -0,0 +1,69 @@
+import { DatePicker, Form, Input, Modal, Select } from "antd";
+import moment from "moment";
+import React from "react";
+import { useModel } from "umi";
+
+interface Props {
+    value?: any,
+    visible?: boolean,
+    onChange?: () => void,
+    onClose?: () => void
+}
+function CheckAccount(props: Props) {
+    const { value = {}, visible = false, onChange, onClose } = props
+    const { userId } = useModel('useOperating.useUser', model => ({ userId: model.state.selectdUserId }))
+    const { getResource, checkGdtAccountGdt } = useModel('useOperating.useAccountyyb')
+    const [form] = Form.useForm()
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        let { accountIds, resourceNames, ...data1 } = data
+        let proms = { ...data1, beginTime: moment(data.beginTime).format('YYYY-MM-DD'), userId, gdtAccountIds: accountIds }
+        checkGdtAccountGdt.run(proms).then(res => {
+            if (res) {
+                onChange && onChange()
+            }
+        })
+    }
+
+    // 搜索资源
+    const handleSearch = (value: string) => {
+        getResource.run({ resourceName: value, resourceType: 0})
+    }
+
+    return <Modal title={`${'GDT'}切号`} open={visible} onOk={handleOk} onCancel={() => { onClose && onClose() }} confirmLoading={checkGdtAccountGdt.loading}>
+        <Form
+            name="basic"
+            form={form}
+            labelCol={{ span: 5 }}
+            wrapperCol={{ span: 19 }}
+            autoComplete="off"
+            initialValues={{ ...value }}
+        >
+            <Form.Item label="账号ID" name="accountIds" rules={[{ type: 'string', required: true, message: '请输入账号!' }]}>
+                <Input.TextArea autoSize placeholder="请输入账号ID(id1,id2,id3)" disabled />
+            </Form.Item>
+            <Form.Item label="当前投放信息" name="resourceNames">
+                <Input.TextArea autoSize disabled />
+            </Form.Item>
+            <Form.Item label="投放信息" name="putResourceId" rules={[{ type: 'number', required: true, message: '请选择投放信息!' }]}>
+                <Select
+                    showSearch
+                    placeholder="选择投放信息"
+                    defaultActiveFirstOption={false}
+                    showArrow={false}
+                    filterOption={false}
+                    onSearch={handleSearch}
+                    notFoundContent={null}
+                >
+                    {getResource?.data?.map((item: { id: number, resourceName: string }) => <Select.Option key={item.id} value={item.id}>{item.resourceName}</Select.Option>)}
+                </Select>
+            </Form.Item>
+            <Form.Item label="开始时间" name="beginTime" rules={[{ type: 'object' as const, required: true, message: '请选择时间!' }]}>
+                <DatePicker />
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(CheckAccount)

+ 12 - 0
src/pages/launchSystemV3/account/const.ts

@@ -0,0 +1,12 @@
+
+/**
+ * 业务单元类型
+ */
+export enum AdUnitType_Enum {
+    NOVEL = '小说',
+    NOVEL_IAA = '小说IAA',
+    GAME = '游戏',
+    GAME_IAA = '游戏IAA',
+    SHOP = '电商',
+    SKIT_IAA = '短剧'
+}

+ 71 - 0
src/pages/launchSystemV3/account/divideIntoGroups.tsx

@@ -0,0 +1,71 @@
+import { useAjax } from "@/Hook/useAjax"
+import { addAccountToGroupApi, delAccountToGroupApi } from "@/services/launchAdq/subgroup"
+import { getArrDifference } from "@/utils/utils"
+import { UsergroupAddOutlined } from "@ant-design/icons"
+import { Button, Checkbox, message, Popover, Spin, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+
+
+interface Props {
+    groupIds: number[],
+    accountId: number,
+    getAdAccountList?: any
+}
+/**
+ * 账号分组至xxx
+ * @param props 
+ */
+const DivideIntoGroups: React.FC<Props> = (props) => {
+
+    /********************************/
+    const { groupIds = [], accountId, getAdAccountList } = props
+    const { getGroupList } = useModel('useLaunchAdq.useAdAuthorize')
+    const [visible, setVisible] = useState<boolean>(false);
+
+    const addAccountToGroup = useAjax((params) => addAccountToGroupApi(params))
+    const delAccountToGroup = useAjax((params) => delAccountToGroupApi(params))
+    /********************************/
+
+    const handleVisibleChange = (newOpen: boolean) => {
+        setVisible(newOpen)
+    }
+    const onChange = (checkedValues: any[]) => {
+        let newGroupid = getArrDifference(checkedValues, groupIds)
+        if (newGroupid.length > 0) {
+            if (checkedValues.length > groupIds.length) {  // 新增
+                addAccountToGroup.run({ currGroupId: newGroupid[0], accountIds: [accountId] }).then(res => {
+                    getAdAccountList.refresh()
+                })
+            } else {  // 删除
+                delAccountToGroup.run({ currGroupId: newGroupid[0], accountIds: [accountId] }).then(res => {
+                    getAdAccountList.refresh()
+                })
+            }
+        }
+    }
+
+    return <Popover
+        content={<Spin spinning={delAccountToGroup.loading || addAccountToGroup.loading}>
+            <Checkbox.Group style={{ width: '100%' }} value={groupIds} onChange={onChange}>
+                <ul className="dig_ul">
+                    {getGroupList?.data?.map((item: { groupName: string, groupId: number }) => <li className="dig_ul__li" key={item.groupId}>
+                        <Checkbox value={item.groupId}>{item.groupName}</Checkbox>
+                    </li>)}
+                </ul>
+            </Checkbox.Group>
+        </Spin>}
+        title="选择分组"
+        trigger="click"
+        open={visible}
+        overlayClassName="dig"
+        onOpenChange={handleVisibleChange}
+    >
+        <Tooltip title="分组">
+            <Button size="small" style={{ color: '#eb2f96' }} onClick={() => setVisible(true)}><UsergroupAddOutlined /></Button>
+        </Tooltip>
+    </Popover>
+}
+
+
+export default React.memo(DivideIntoGroups)

+ 156 - 0
src/pages/launchSystemV3/account/groupLeft.tsx

@@ -0,0 +1,156 @@
+import { useAjax } from "@/Hook/useAjax"
+import { addGroupApi, delGroupApi, editGroupApi, getGroupListApi } from "@/services/launchAdq/subgroup"
+import { DeleteOutlined, FormOutlined, PlusOutlined } from "@ant-design/icons"
+import { Button, Input, message, Popconfirm, Space, Spin } from "antd"
+import React, { useEffect, useRef, useState } from "react"
+import { useModel } from "umi"
+import './index.less'
+
+/**
+ * 分组管理Left 菜单
+ */
+interface Props {
+    onChange?: (groupId?: number) => void
+    value?: number
+}
+const GroupLeft: React.FC<Props> = (props) => {
+
+    /*************************/
+    const { onChange, value } = props
+    const [isNewGrouping, setIsNewGrouping] = useState<boolean>(false);  // 是否新增分组
+    const inputRef = useRef<any>(null);
+    const [groupData, setGroupData] = useState<{ groupName: string, remark?: string, groupId?: number }>({ groupName: '' }) // 分组添加
+    const [selectAccData, setSelectAccData] = useState<{ id: number, mpName: string }[]>([])  // 选中
+    const [groupId, setGroupId] = useState<number>(value || 0)
+    const { getGroupList } = useModel('useLaunchAdq.useAdAuthorize')
+
+    const addGroup = useAjax((params) => addGroupApi(params))
+    const editGroup = useAjax((params) => editGroupApi(params))
+    const delGroup = useAjax((params) => delGroupApi(params))
+    /*************************/
+
+    useEffect(() => {
+        if (value) {
+            setGroupId(value || 0)
+        }
+    }, [value])
+
+    /** 点击新增处理聚焦 */
+    useEffect(() => {
+        if (isNewGrouping && inputRef?.current) {
+            inputRef.current!.focus({
+                cursor: 'end',
+            });
+        }
+    }, [inputRef, isNewGrouping])
+
+    /** 新增修改分组 */
+    const addEditHandle = () => {
+        if (groupData?.groupName) {
+            if (groupData?.groupId) {
+                editGroup.run(groupData).then((res: any) => {
+                    message.success('修改成功')
+                    setGroupData({ groupName: '' })
+                    setIsNewGrouping(false)
+                    getGroupList.refresh()
+                })
+            } else {
+                addGroup.run(groupData).then((res: any) => {
+                    message.success('添加成功')
+                    setGroupData({ groupName: '' })
+                    setIsNewGrouping(false)
+                    getGroupList.refresh()
+                })
+            }
+        } else {
+            message.error('请填写分组名称')
+        }
+    }
+
+    /** 删除分组 */
+    const delGroupHandle = (id: number) => {
+        delGroup.run({ groupId: id }).then(res => {
+            message.success('删除成功')
+            getGroupList.refresh()
+            if (groupId === id) {
+                setGroupId(0)
+                onChange?.(undefined)
+            }
+        })
+    }
+
+    return <div className="groupLeft">
+        <div className="groupLeft_header">
+            <div>全部分组</div>
+            <Button type="link" icon={<PlusOutlined />} className="stop" onClick={(e) => {
+                e.stopPropagation()
+                setGroupData({ groupName: '' })
+                setIsNewGrouping(true)
+            }}>新增分组</Button>
+        </div>
+        <div className="groupLeft_content">
+            {isNewGrouping && <div className="groupLeft_content_item">
+                <div className="edit stop">
+                    <Space direction="vertical" className="stop">
+                        <Space className="stop">
+                            <div style={{ width: 30 }} className="stop">组名</div>
+                            <Input placeholder="输入分组名称(最多10个字)" className="stop" maxLength={10} ref={inputRef} onChange={(e) => { setGroupData({ ...groupData, groupName: e.target.value }) }} value={groupData?.groupName} />
+                        </Space>
+                        <div style={{ display: 'flex', justifyContent: 'flex-end' }} className="stop">
+                            <Space className="stop">
+                                <Button size="small" className="stop" onClick={() => { setIsNewGrouping(false); setGroupData({ groupName: '' }) }}>取消</Button>
+                                <Button type="primary" className="stop" size="small" onClick={addEditHandle} loading={getGroupList?.loading}>确定</Button>
+                            </Space>
+                        </div>
+                    </Space>
+                </div>
+            </div>}
+            <Spin spinning={getGroupList.loading}>
+                <div className="groupLeft_content_item">
+                    <div className={`con ${groupId === 0 ? 'select' : ''}`} onClick={() => { setGroupId(0); onChange?.(undefined); setSelectAccData([]) }}>
+                        <div className="left">
+                            <div className="title">全部</div>
+                        </div>
+                    </div>
+                </div>
+                {getGroupList && Array.isArray(getGroupList?.data) && getGroupList?.data?.map((item: { groupName: string, groupId: number }, index: number) => <div className="groupLeft_content_item" key={item.groupId || index}>
+                    {groupData?.groupId && groupData?.groupId === item?.groupId ? <div className="edit stop">
+                        <Space direction="vertical" className="stop">
+                            <Space className="stop">
+                                <div style={{ width: 40 }} className="stop">组名</div>
+                                <Input placeholder="输入分组名称(最多10个字)" className="stop" maxLength={10} ref={inputRef} onChange={(e) => { setGroupData({ ...groupData, groupName: e.target.value }) }} value={groupData?.groupName} />
+                            </Space>
+                            <div style={{ display: 'flex', justifyContent: 'flex-end' }} className="stop">
+                                <Space className="stop">
+                                    <Button size="small" className="stop" onClick={() => { setIsNewGrouping(false); setGroupData({ groupName: '' }) }}>取消</Button>
+                                    <Button type="primary" className="stop" size="small" onClick={addEditHandle} loading={editGroup?.loading}>修改</Button>
+                                </Space>
+                            </div>
+                        </Space>
+                    </div> : <div className={`con ${groupId === item.groupId ? 'select' : ''}`} onClick={() => { setGroupId(item.groupId); onChange?.(item.groupId); setSelectAccData([]) }} key={item.groupId || index}>
+                        <div className="left">
+                            <div className="title">{item.groupName}</div>
+                        </div>
+                        <div className="right">
+                            <Space>
+                                <FormOutlined onClick={(e) => { e.stopPropagation(); setGroupData({ ...item }); setIsNewGrouping(false) }} />
+                                <Popconfirm
+                                    title="确定删除?"
+                                    onConfirm={() => { delGroupHandle(item.groupId) }}
+                                    okText="确定"
+                                    cancelText="取消"
+                                >
+                                    <DeleteOutlined style={{ color: 'red' }} />
+                                </Popconfirm>
+                            </Space>
+                        </div>
+                    </div>}
+
+                </div>)}
+            </Spin>
+        </div>
+    </div>
+}
+
+
+export default React.memo(GroupLeft)

+ 162 - 0
src/pages/launchSystemV3/account/index.less

@@ -0,0 +1,162 @@
+.verticalCenter {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  margin-left: 5px;
+}
+
+.name-wrapper>p {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.boxCol {
+  display: flex;
+
+  strong {
+    width: 25%;
+    text-align: right;
+  }
+
+  span {
+    color: #999;
+    width: 75%;
+  }
+}
+
+.manage {
+  height: calc(100% - 42px);
+  display: flex;
+  justify-content: flex-start;
+
+  &__left {
+    width: calc(100% - 200px);
+  }
+}
+
+.groupLeft {
+  width: 200px;
+  height: 100%;
+  background-color: #fff;
+
+  &_header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 50px;
+    border-bottom: 1px solid rgb(236, 236, 236);
+    background-color: #f8f8f8;
+
+    &>div {
+      font-size: 16px;
+      font-weight: 600;
+      padding: 0 10px;
+    }
+  }
+
+  &_content {
+    height: calc(100% - 50px);
+    overflow-y: auto;
+
+    &_item {
+      &>.con {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 5px 12px;
+        box-sizing: border-box;
+        height: 50px;
+        border-bottom: 1px solid rgb(236, 236, 236);
+        transition: .1s;
+        box-sizing: border-box;
+        cursor: pointer;
+        border-right: 2px solid #fff;
+
+        >.left {
+          width: calc(100% - 45px);
+
+          >div {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+        }
+
+        >.right {
+          width: 38px;
+        }
+
+        .title {
+          color: #000;
+          font-weight: 500;
+        }
+
+        .subTitle {
+          font-size: 12px;
+          color: rgb(122, 122, 122);
+        }
+
+        &:hover {
+          background-color: #e6f7ff;
+          border-right-color: #1890ff;
+
+          .title {
+            color: #1890ff;
+          }
+
+          .subTitle {
+            color: #1890ff;
+          }
+        }
+
+        &.select {
+          background-color: #e6f7ff;
+          border-right-color: #1890ff;
+
+          .title {
+            color: #1890ff;
+          }
+
+          .subTitle {
+            color: #1890ff;
+          }
+        }
+      }
+
+      &>.edit {
+        padding: 10px 12px;
+        box-sizing: border-box;
+        border-bottom: 1px solid rgb(236, 236, 236);
+
+        // font-size: 12px;
+        .ant-input-affix-wrapper {
+          border-radius: 4px;
+        }
+      }
+    }
+
+  }
+}
+
+
+.dig {
+
+  &_ul {
+    padding: 0;
+    margin-bottom: 0;
+
+    &__li {
+      padding: 4px 16px;
+      box-sizing: border-box;
+      cursor: pointer;
+
+      &:hover {
+        color: #1890ff;
+      }
+    }
+  }
+
+  .ant-popover-inner-content {
+    padding: 0 0 8px 0;
+  }
+}

+ 407 - 0
src/pages/launchSystemV3/account/index.tsx

@@ -0,0 +1,407 @@
+
+import HocError from '@/Hoc/HocError'
+import { Col, Modal, Row, Input, message, Space, Tabs, Button, Select } from 'antd'
+import React, { useCallback, useEffect, useState } from 'react'
+import { columnsMp } from './tableConfig'
+import { useAjax } from '@/Hook/useAjax'
+import { getAdAccountListApi, GetAdAccountParams, putAdAccountApi } from '@/services/launchAdq/adAuthorize'
+import style from './index.less'
+import TableData from '@/pages/launchSystemNew/components/TableData'
+import GroupLeft from './groupLeft'
+import QQAuth from './qqAuto'
+import { MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined, SwapOutlined } from '@ant-design/icons'
+import { getAdAccountAllOfMember, getErpUserAll, getServiceProviderAll, putConfigServiceProvider, putRefreshTokenApi } from '@/services/launchAdq/adq'
+import AddAccountToGroup from './addAccountToGroup'
+import { delAccountToGroupApi } from '@/services/launchAdq/subgroup'
+import { useModel } from 'umi'
+import ChangeRecord from './changeRecord'
+import CheckAccount from './checkAccount'
+import AppointPut from './appointPut'
+import SetEarlyWarningsAccount from '@/components/EarlyWarning/setEarlyWarningsAccount'
+import { AdUnitType_Enum } from './const'
+import TeamMembers from '@/pages/launchSystemNew/components/teamMembers'
+
+/** 投放管理 */
+const AdAuthorize: React.FC = () => {
+    let [visible, setVisible] = useState(false)
+    /*************************/
+    const { groupListInit } = useModel('useLaunchAdq.useAdAuthorize')
+    const userInfo = useModel('@@initialState', model => model.initialState?.currentUser)
+    const [queryForm, setQueryForm] = useState<GetAdAccountParams>({ pageNum: 1, pageSize: 20, appType: 1 })
+    const [remarkData, set_remarkData] = useState<{ visible: boolean, remark: string, data: any }>({
+        visible: false,
+        remark: '',
+        data: null
+    })
+    const [activeKey, setActiveKey] = useState<string>('1')
+    const [showLeft, setShowLeft] = useState<boolean>(false)
+    const [crShow, setCrShow] = useState<boolean>(false) // 变更记录控制
+    const [crData, setCrData] = useState<{ name: number, id: number } | null>(null)
+    const [checkAccShow, setCheckAccShow] = useState<boolean>(false)
+    const [openServer, setOpenServer] = useState<any[]>([])//配置服务商数据
+    const [serverName, setServerName] = useState<any>(null)//选择的服务商名称
+    const [data, setData] = useState<{
+        putResourceId?: number | undefined,
+        beginTime?: string | undefined,
+        gdtAccountId?: number,
+        accountIds?: string,
+        accountId?: string,
+        gdtAccountIds?: string,
+        advertiserId?: string,
+        accountName?: string,
+        account?: string,
+        accountPassword?: string,
+        quickAppAccountIds?: string
+        resourceNames?: string
+    } | undefined>(undefined)
+    const [selectAccData, setSelectAccData] = useState<any[]>([])
+    const [puShow, setPuShow] = useState<boolean>(false)
+    const [puData, setPuData] = useState<any[]>([])
+    const [switchType, setSwitchType] = useState<'account' | 'putUser' | 'setServer' | 'refreshToken'>('account')
+    const putRemark = useAjax((adAccountId: any, remark: any) => putAdAccountApi(adAccountId, remark))
+    const delAccountToGroup = useAjax((params) => delAccountToGroupApi(params))
+    const getAdAccountList = useAjax((params) => getAdAccountListApi(params), { formatResult: true })
+    const allOfMember = useAjax(() => getAdAccountAllOfMember(), { formatResult: true })
+    const erpUserALL = useAjax(() => getErpUserAll(), { formatResult: true })
+    const api_getServiceProviderAll = useAjax(() => getServiceProviderAll(), { formatResult: true })
+    const api_putConfigServiceProvider = useAjax((params) => putConfigServiceProvider(params), { formatResult: true })
+    const putRefreshToken = useAjax((params) => putRefreshTokenApi(params), { formatResult: true })
+    /*************************/
+
+    useEffect(() => {
+        groupListInit()
+        !erpUserALL.data && erpUserALL.run()
+        !api_getServiceProviderAll.data && api_getServiceProviderAll.run()
+    }, [])
+
+    useEffect(() => {
+        getList()
+    }, [queryForm])
+
+    /** 获取账号列表 */
+    const getList = () => {
+        let params = JSON.parse(JSON.stringify(queryForm))
+        if (params.accountIds) {
+            params.accountIds = params.accountIds.split(/[\,\,]/)
+        } else {
+            delete params?.accountIds
+        }
+        getAdAccountList.run(params)
+    }
+
+    const remark = () => {
+        if (remarkData.remark && remarkData.data) {
+            putRemark.run(remarkData.data.accountId, remarkData.remark).then(res => {
+                set_remarkData({ ...remarkData, visible: false, remark: '', data: null })
+                getList()
+            })
+        } else {
+            message.error('请输入备注!')
+        }
+
+    }
+    const edit = useCallback((data: any) => {
+        set_remarkData({ ...remarkData, visible: true, data, remark: data.remark })
+    }, [remarkData])
+
+    /** 移除分组里账号 */
+    const del = (groupId: number, accountId: number) => {
+        delAccountToGroup.run({ currGroupId: groupId, accountIds: [accountId] }).then(res => {
+            message.success('移出成功')
+            getAdAccountList.refresh()
+        })
+    }
+
+    /** 切号 */
+    const checkAccount = (value: any[]) => {
+        let ids = value?.map((item: any) => item.id)
+        setData({ resourceNames: value?.map((item: any) => item.putResourceName).toString(), accountIds: value?.map((item: any) => item.accountId).toString(), gdtAccountIds: ids.toString(), putResourceId: undefined, beginTime: undefined })
+        setCheckAccShow(true);
+    }
+
+    /** 变更记录 */
+    const changeRecord = (name: number, id: number) => {
+        setCrData({ name, id })
+        setCrShow(true)
+    }
+
+    /** 指派投手 */
+    const putUserHandle = (data: any[]) => {
+        setPuData(data)
+        setPuShow(true)
+    }
+    // 批量配置服务商
+    const setServiceProviderName = (accountIds: any) => {
+        api_putConfigServiceProvider.run({ accountIds, serviceProviderName: serverName.label }).then(res => {
+            if (res.data) {
+                message.success("配置成功!")
+                setOpenServer([]);
+                setServerName(null)
+                getAdAccountList.refresh()
+            } else {
+                message.error("配置失败!")
+            }
+        })
+    }
+
+    /** 刷新token */
+    const handleRefreshToken = () => {
+        putRefreshToken.run(selectAccData.map(item => item.accountId)).then(res => {
+            console.log(res)
+            if (res?.data?.length) {
+                message.error(res.data.toString())
+            } else {
+                setSelectAccData([])
+                message.success('刷新成功')
+            }
+        })
+    }
+
+    return <div style={{ height: '100%' }}>
+        <Tabs
+            tabBarStyle={{ marginBottom: 1 }}
+            activeKey={activeKey}
+            type="card"
+            // tabBarExtraContent={<Button type='primary' onClick={()=>setVisible(true)}><PlusOutlined />广告账号授权</Button>}
+            onChange={(activeKey) => {
+                if (activeKey !== 'contract') {
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    delete newQueryForm?.groupId
+                    delete newQueryForm?.putUserId
+                    setQueryForm(newQueryForm)
+                    setActiveKey(activeKey)
+                } else {
+                    setShowLeft(!showLeft)
+                }
+            }}
+        >
+            <Tabs.TabPane tab='我的' key='1' />
+            <Tabs.TabPane tab='组员' key='2' />
+            <Tabs.TabPane tab={showLeft ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} key='contract' />
+        </Tabs>
+
+        <div className={style.manage}>
+            {!showLeft && activeKey === '1' && <GroupLeft onChange={(groupId) => setQueryForm({ ...queryForm, groupId, pageNum: 1 })} value={queryForm?.groupId} />}
+            {!showLeft && activeKey === '2' && <TeamMembers allOfMember={allOfMember} onChange={(putUserId) => setQueryForm({ ...queryForm, putUserId, pageNum: 1 })} value={queryForm?.putUserId} />}
+
+            <div className={style.manage__left} style={showLeft ? { width: '100%' } : { width: 'calc(100% - 200px)' }}>
+                <TableData
+                    ajax={getAdAccountList}
+                    dataSource={getAdAccountList?.data?.data?.records}
+                    loading={getAdAccountList?.loading}
+                    columns={() => columnsMp(edit, setOpenServer, del, checkAccount, changeRecord, putUserHandle, () => getAdAccountList.refresh(), activeKey, userInfo?.userId?.toString(), queryForm?.groupId, getAdAccountList)}
+                    total={getAdAccountList?.data?.data?.total}
+                    page={getAdAccountList?.data?.data?.current}
+                    pageSize={getAdAccountList?.data?.data?.size}
+                    size="small"
+                    scroll={{ y: 600 }}
+                    leftChild={<Space wrap>
+                        <Select
+                            style={{ width: 90 }}
+                            placeholder="选择应用类型"
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, appType: e })
+                            }}
+                            value={queryForm.appType}
+                            options={[
+                                { label: '小说', value: 1 },
+                                { label: '游戏', value: 2 },
+                                { label: '短剧', value: 3 }
+                            ]}
+                        />
+                        <Select
+                            style={{ width: 120 }}
+                            placeholder="选择操作类型"
+                            onChange={(e) => {
+                                setSwitchType(e);
+                                setSelectAccData([])
+                            }}
+                            value={switchType}
+                            dropdownMatchSelectWidth={false}
+                            options={[
+                                { label: '批量切号', value: 'account' },
+                                { label: '批量指派投放助理', value: 'putUser' },
+                                { label: '批量配置服务商', value: 'setServer' },
+                                { label: '批量刷新TOKEN', value: 'refreshToken' }
+                            ]}
+                        />
+                        <Input.TextArea
+                            placeholder="多个广告账号以,隔开(id1,id2)"
+                            allowClear
+                            style={{ minWidth: 180 }}
+                            value={queryForm?.accountIds}
+                            rows={1}
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, accountIds: e.target.value.replaceAll(/\s/ig, '') })
+                            }}
+                        />
+                        <Select
+                            style={{ width: 110 }}
+                            placeholder="是3.0账号?"
+                            allowClear
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, addV3: e })
+                            }}
+                            value={queryForm?.addV3}
+                            options={[{ label: '是', value: true }, { label: '否', value: false }]}
+                        />
+                        <Select
+                            style={{ width: 110 }}
+                            placeholder="是业务单元?"
+                            allowClear
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, adUnitAccount: e })
+                            }}
+                            value={queryForm?.adUnitAccount}
+                            options={[{ label: '是', value: true }, { label: '否', value: false }]}
+                        />
+                        <Select
+                            style={{ width: 120 }}
+                            placeholder="业务单元类型"
+                            allowClear
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, adUnitType: e })
+                            }}
+                            value={queryForm?.adUnitType}
+                            options={Object.keys(AdUnitType_Enum).filter(item => ['NOVEL', 'NOVEL_IAA', 'SHOP'].includes(item)).map(key => ({ label: AdUnitType_Enum[key as keyof typeof AdUnitType_Enum], value: key }))}
+                        />
+                        <Button onClick={getList} type='primary' loading={getAdAccountList.loading}>搜索</Button>
+                        <AddAccountToGroup onChange={() => getAdAccountList.refresh()} />
+                        {/* <Button type='primary' onClick={() => setVisible(true)}><PlusOutlined />广告账号授权</Button> */}
+                        {switchType === 'account' ?
+                            (selectAccData?.length > 0 && <>
+                                <Button type="primary" ghost icon={<SwapOutlined />} onClick={() => { checkAccount(selectAccData) }}>批量切号</Button>
+                                <SetEarlyWarningsAccount onChange={() => { setSelectAccData([]); }} accountIds={selectAccData.map(item => item.accountId)?.toString()} />
+                            </>) :
+                            switchType === 'putUser' ? (selectAccData?.length > 0 && <Button type="primary" ghost icon={<SwapOutlined />} onClick={() => { putUserHandle(selectAccData) }}>批量指派投放助理</Button>) : switchType === 'setServer' ?
+                                selectAccData?.length > 0 && <Button type="primary" ghost icon={<SwapOutlined />} onClick={() => { setOpenServer(selectAccData) }}>批量配置服务商</Button> :
+                                selectAccData.length > 0 && <Button type="primary" ghost icon={<SwapOutlined />} loading={putRefreshToken.loading} onClick={() => { handleRefreshToken() }}>批量刷新TOKEN</Button>
+                        }
+                    </Space>}
+                    rowSelection={{
+                        selectedRowKeys: selectAccData?.map((item: any) => item.id?.toString()),
+                        getCheckboxProps: (record: any) => ({
+                            disabled: switchType === 'putUser' ? activeKey === '2' || userInfo?.userId !== record?.putUserInfo?.userId : false
+                        }),
+                        onSelect: (record: { id: number, mpName: string }, selected: boolean) => {
+                            if (selected) {
+                                selectAccData.push({ ...record })
+                                setSelectAccData([...selectAccData])
+                            } else {
+                                let newSelectAccData = selectAccData.filter((item: { id: number }) => item.id !== record.id)
+                                setSelectAccData([...newSelectAccData])
+                            }
+                        },
+                        onSelectAll: (selected: boolean, selectedRows: { id: number }[], changeRows: { id: number }[]) => {
+                            if (selected) {
+                                let newSelectAccData = [...selectAccData]
+                                changeRows.forEach((item: { id: number }) => {
+                                    let index = newSelectAccData.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index === -1) {
+                                        newSelectAccData.push({ ...item })
+                                    }
+                                })
+                                setSelectAccData([...newSelectAccData])
+                            } else {
+                                let newSelectAccData = selectAccData.filter((item: { id: number }) => {
+                                    let index = changeRows.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index !== -1) {
+                                        return false
+                                    } else {
+                                        return true
+                                    }
+                                })
+                                setSelectAccData([...newSelectAccData])
+                            }
+                        }
+                    }}
+                    onChange={(props: any) => {
+                        let { pagination } = props
+                        let { current, pageSize } = pagination
+                        setQueryForm({ ...queryForm, pageNum: current, pageSize })
+                    }}
+                />
+            </div>
+        </div>
+        {/* 批量设置服务商 */}
+        {openServer.length > 0 && <Modal
+            title="配置服务商"
+            onOk={() => setServiceProviderName(openServer?.map(item => item.accountId))}
+            onCancel={() => { setOpenServer([]); setServerName(null) }}
+            open={openServer.length > 0}
+            confirmLoading={api_putConfigServiceProvider.loading}
+        >
+            <Row gutter={[20, 20]}>
+                <Col span={24}>
+                    <Row>
+                        <Col span={3}><b>广告主:</b></Col>
+                        <Col span={21}>
+                            {openServer.map((item, index) => {
+                                return index === openServer.length - 1 ? item.accountId : item.accountId + ','
+                            })}
+                        </Col>
+                    </Row>
+                </Col>
+                <Col span={24}>
+                    <Row>
+                        <Col span={3}><b>服务商:</b></Col>
+                        <Col span={21}>
+                            <Select
+                                style={{ minWidth: 200 }}
+                                showSearch
+                                allowClear
+                                placeholder="请选择服务商"
+                                optionFilterProp="children"
+                                onChange={(value: any, option: any) => {
+                                    setServerName(option)
+                                }}
+                                filterOption={(input, option: any) =>
+                                    (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                                }
+                                value={serverName}
+                                options={api_getServiceProviderAll?.data?.data?.map((item: { id: any, serviceProviderName: any }) => {
+                                    return { value: item.id, label: item.serviceProviderName }
+                                })}
+                            />
+                        </Col>
+                    </Row>
+                </Col>
+            </Row>
+
+        </Modal>}
+        {/* 广告授权 */}
+        {visible && <QQAuth qqVisible={visible} callBack={() => setVisible(false)} />}
+        {/* 变更记录 */}
+        {crShow && <ChangeRecord visible={crShow} data={crData} onClose={() => { setCrShow(false); setCrData(null) }} />}
+        {/* 切号 */}
+        {checkAccShow && <CheckAccount value={data} visible={checkAccShow} onChange={() => { getList(); setCheckAccShow(false); setSelectAccData([]) }} onClose={() => { setCheckAccShow(false) }} />}
+        {/* 指派 */}
+        {puShow && <AppointPut value={puData} visible={puShow} onClose={() => { setPuShow(false) }} allOfMember={erpUserALL} onChange={() => { setPuShow(false); getAdAccountList.refresh(); setSelectAccData([]) }} />}
+
+        {remarkData.visible && <Modal
+            open={remarkData.visible}
+            title='编辑账户'
+            onCancel={() => { set_remarkData({ ...remarkData, visible: false, data: null }) }}
+            onOk={remark}
+            confirmLoading={putRemark.loading}
+        >
+            <Row gutter={[20, 20]}>
+                <Col span={24} className={style.boxCol}><strong>广告主ID:</strong><span>{remarkData?.data.accountId}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>类型:</strong><span>{remarkData?.data.sourceType === 0 ? '微信' : 'QQ'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>公众号信息:</strong><span>{remarkData?.data.wechatAccountName || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>企业名称:</strong><span>{remarkData?.data.corporationName || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>服务商ID列表:</strong><span>{remarkData?.data.agencyIdList ? remarkData.data.agencyIdList?.join() : '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>行业ID:</strong><span>{remarkData?.data.systemIndustryId || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>授权状态:</strong><span>{remarkData?.data.authStatus || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>日限额(分):</strong><span>{remarkData?.data.dailyBudget || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>授权时间:</strong><span>{remarkData?.data.createTime || '无'}</span></Col>
+                <Col span={24} className={style.boxCol}><strong>备注:</strong><span><Input.TextArea rows={5} maxLength={200} value={remarkData.remark} onChange={(e) => {
+                    let value = e.target.value
+                    set_remarkData({ ...remarkData, remark: value })
+                }} /></span></Col>
+            </Row>
+        </Modal>}
+    </div>
+}
+
+export default HocError(AdAuthorize)

+ 161 - 0
src/pages/launchSystemV3/account/qqAuto.tsx

@@ -0,0 +1,161 @@
+import { useCallback, useState } from "react"
+import React from 'react'
+import { Col, Row, Radio, Input, Button, message, Space, Image, Modal, Popconfirm } from "antd";
+let name = {
+    0: '小说账号失败列表:',
+    1: '游戏账号失败列表:',
+}
+var time: any = null
+let api = "http://47.99.157.216:8023"
+// let api= "http://127.0.0.1:8023"
+function QQAuth(props: { qqVisible: boolean, callBack: () => void }) {
+    const { qqVisible, callBack } = props
+    let [visible, setVisible] = useState(false)
+    let [loading, setLoading] = useState(false)
+    let [codeUrl, setCodeUrl] = useState('')
+    let [err, setErr] = useState({})
+    let [data, setData] = useState({
+        userId: localStorage.getItem('userId'),
+        adAppType: 0,
+        users: [],
+        callbackPage: 'http%3A%2F%2Ferp.zanxiangnet.com%2FadCode',
+        authorization: `Bearer ${sessionStorage.getItem('Admin-Token')}`
+    })
+    let adAppIdonChange = useCallback((checkedValue) => {
+        let v = checkedValue.target.value
+        console.log("checkedValue===>", v)
+        setData({ ...data, adAppType: v })
+    }, [data])
+    let submit = useCallback(() => {
+        if (data.users.some(user => isNaN(user))) {
+            message.error('账号请使用数字!!!!!!')
+            return
+        }
+        setLoading(true)
+        fetch(api + '/qq/auth', {
+            method: 'POST',
+            headers: {
+                "content-type": "application/json",
+            },
+            body: JSON.stringify(data)
+        }).then(res => res.json()).then(r => {
+            if (r?.data?.codeImgUrl) {
+                setCodeUrl(r?.data?.codeImgUrl)
+                setVisible(true)
+                let n = 0
+                time = setInterval(() => {
+                    if (n < 60) {
+                        n += 1
+                        fetch(`${api}/qq/isOk?userId=${localStorage.getItem('userId')}`, {
+                            method: 'GET'
+                        }).then(res => res.json()).then(r => {
+                            if (r.data) {
+                                setVisible(false)
+                                setCodeUrl("")
+                                clearInterval(time)
+                                setLoading(false)
+                            }
+                        })
+                    } else {
+                        message.error('扫码超时请重新扫码!')
+                        setVisible(false)
+                        setCodeUrl("")
+                        clearInterval(time)
+                        setLoading(false)
+                    }
+
+                }, 1000)
+            } else {
+                message.success('已经开始授权,稍后自行查看结果!')
+                setLoading(false)
+            }
+            console.log(r)
+        })
+    }, [data])
+    let query = () => {
+        fetch(`${api}/qq/queryErr?userId=${localStorage.getItem('userId')}`, {
+            method: 'GET'
+        }).then(res => res.json()).then(r => {
+            console.log(r)
+            setErr(r.data)
+        })
+    }
+    let del = () => {
+        fetch(`${api}/qq/delCookie?userId=${localStorage.getItem('userId')}`, {
+            method: 'GET'
+        }).then(res => res.json()).then(r => {
+            console.log(r)
+            message.success('清理成功!!!!请重新扫码授权登录!!!')
+        })
+    }
+    let userIdChange = useCallback((e) => {
+        let value = e.target.value
+        let arr = value.split(/,|,/)
+        let newArr = arr?.map((str: string) => Number(str.replace(/\s/ig, '')))
+        setData({ ...data, users: newArr })
+    }, [data])
+    return <Modal
+        open={qqVisible}
+        footer={null}
+        width={800}
+        onCancel={callBack}
+        maskClosable={false}
+    >
+        <div>
+            <Row>
+                <Col span='24'>
+                    <Row>
+                        <Col span={3}><h3>平台选择:</h3></Col>
+                        <Col span={20}>
+                            <Radio.Group style={{ width: '100%' }} onChange={adAppIdonChange} defaultValue={data.adAppType}>
+                                <Radio value={0}>小说</Radio>
+                                {/* <Radio value={1}>游戏</Radio> */}
+                            </Radio.Group>
+                        </Col>
+                    </Row>
+                </Col>
+                <Col span='24'>
+                    <Row>
+                        <Col span={3}><h3>账号ID:</h3></Col>
+                        <Col span={20}> <Input.TextArea placeholder='多个账号请以,号隔开例:123456,222333,444444' rows={5} onChange={userIdChange} allowClear></Input.TextArea></Col>
+                    </Row>
+                </Col>
+            </Row>
+            <Row style={{ display: 'flex', justifyContent: 'center', marginTop: 20 }}>
+                <Space>
+                    <Button type='primary' onClick={submit} loading={loading}>提交</Button>
+                    <Button type='default' onClick={query} >查询授权失败账号</Button>
+                    <Popconfirm title={'清除账号状态将清除所有失败记录,并清空上一次扫码授权状态!'} onConfirm={del} >
+                        <Button type='primary' danger >清除账号状态</Button>
+                    </Popconfirm>
+
+                </Space>
+            </Row>
+            <Row>
+                {
+                    Object.keys(err).map((key, i) => {
+                        return <Col key={i} span={24}>
+                            <div>
+                                <h4>{name[key]}</h4>
+                                <p>{err[key]?.join()}</p>
+                            </div>
+                        </Col>
+                    })
+                }
+            </Row>
+            <Modal
+                open={visible}
+                footer={null}
+                width={200}
+                onCancel={() => setVisible(false)}
+                maskClosable={false}
+            >
+                <Image
+                    style={{ width: 150 }}
+                    src={codeUrl} />
+            </Modal>
+        </div>
+    </Modal>
+
+}
+export default QQAuth

+ 270 - 0
src/pages/launchSystemV3/account/tableConfig.tsx

@@ -0,0 +1,270 @@
+import { FundStatusEnum } from "@/services/launchAdq/enum"
+import { EditOutlined, FileSearchOutlined, SettingOutlined, SwapOutlined, UploadOutlined, UserSwitchOutlined } from "@ant-design/icons"
+import { Badge, Button, Popconfirm, Space, Tooltip } from "antd"
+import React from "react"
+import DivideIntoGroups from "./divideIntoGroups"
+import './index.less'
+import OpenV3All from "@/pages/launchSystemNew/account/components/openV3All"
+import { AdUnitType_Enum } from "./const"
+import OpenV3 from "@/pages/launchSystemNew/account/components/openV3"
+
+export function columnsMp(
+    edit: (params: any) => void,
+    setOpenServer: (data: any) => void,
+    del: (groupId: number, accountId: number) => void,
+    checkAccount: (value: any[]) => void,
+    changeRecord: (accountId: number, id: number) => void,
+    putUserHandle: (data: any) => void,
+    v3Change: () => void,
+    activeKey: string,
+    userId: string | undefined,
+    groupId?: number,
+    getAdAccountList?: any
+): any {
+    return [
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            align: 'center',
+            width: 75,
+            fixed: 'left'
+        },
+        {
+            title: '广告主ID',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            align: 'center',
+            width: 80,
+            fixed: 'left'
+        },
+        {
+            title: '类型',
+            dataIndex: 'sourceType',
+            key: 'sourceType',
+            align: 'center',
+            width: 60,
+            render: (a: any, b: any) => {
+                return <span>{a == 0 ? '微信' : 'QQ'}</span>
+            }
+        },
+        {
+            title: '是否业务单元账号',
+            dataIndex: 'adUnitAccount',
+            key: 'adUnitAccount',
+            align: 'center',
+            width: 75,
+            render: (a: boolean) => {
+                return <span>{a ? '是' : '否'}</span>
+            }
+        },
+        {
+            title: '业务单元账号ID',
+            dataIndex: 'adUnitAccountId',
+            key: 'adUnitAccountId',
+            align: 'center',
+            width: 90,
+            ellipsis: true,
+            render: (a: any) => {
+                return <span>{a || '--'}</span>
+            }
+        },
+        {
+            title: '业务单元类型',
+            dataIndex: 'adUnitType',
+            key: 'adUnitType',
+            align: 'center',
+            width: 75,
+            render: (a: string) => {
+                return AdUnitType_Enum[a as keyof typeof AdUnitType_Enum] || '--'
+            }
+        },
+        {
+            title: '投手',
+            dataIndex: 'putUserInfo',
+            key: 'putUserInfo',
+            width: 70,
+            align: 'center',
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return <span>{a?.nickname || '-1'}</span>
+            }
+        },
+        {
+            title: '投放助理',
+            dataIndex: 'accountUsers',
+            key: 'accountUsers',
+            width: 70,
+            align: 'center',
+            ellipsis: true,
+            render: (a: any[], b: any) => {
+                return a && a?.length > 0 ? a.map((item: { putUserName: string, putUseId: number }) => item.putUserName).toString() : '-1'
+            }
+        },
+        {
+            title: '投放信息',
+            dataIndex: 'putResourceName',
+            key: 'putResourceName',
+            width: 110,
+            align: 'center',
+            ellipsis: true,
+        },
+        {
+            title: '腾讯备注',
+            dataIndex: 'memo',
+            key: 'memo',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+        },
+        {
+            title: '本地备注',
+            dataIndex: 'remark',
+            key: 'remark',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+        },
+        {
+            title: '告警名称',
+            dataIndex: 'warningRuleList',
+            key: 'warningRuleList',
+            align: 'center',
+            width: 90,
+            ellipsis: true,
+            render: (a: any[], b: any) => {
+                return a?.length > 0 ? a?.[0]?.ruleName : '--'
+            }
+        },
+        // {
+        //     title: '公众号信息',
+        //     dataIndex: 'wechatAccountName',
+        //     key: 'wechatAccountName',
+        //     width: 150,
+        //     align: 'center',
+        //     render: (a: any, b: any) => {
+        //         return <div className="verticalCenter">
+        //             <div><strong>{a}</strong></div>
+        //             <div style={{ color: "rgb(136, 136, 136)", fontSize: 13 }}>{b?.wechatAccountId}</div>
+        //         </div>
+        //     }
+        // },
+        {
+            title: '企业名称',
+            dataIndex: 'corporationName',
+            key: 'corporationName',
+            width: 150,
+            align: 'center',
+            ellipsis: true
+        },
+        {
+            title: '服务商',
+            dataIndex: 'serviceProviderName',
+            key: 'serviceProviderName',
+            width: 150,
+            align: 'center',
+            ellipsis: true
+        },
+        // {
+        //     title: '服务商ID列表',
+        //     dataIndex: 'agencyIdList',
+        //     key: 'agencyIdList',
+        //     width: 130,
+        //     align: 'center',
+        //     render: (a: any) => {
+        //         return <Tooltip title={a}>
+        //             <div className="name-wrapper">
+        //                 <p>{ a }</p>
+        //             </div>
+        //         </Tooltip>
+        //     }
+        // },
+        {
+            title: '行业ID',
+            dataIndex: 'systemIndustryId',
+            key: 'systemIndustryId',
+            width: 105,
+            align: 'center',
+            render: (a: any) => {
+                return <Tooltip title={a}>
+                    <div className="name-wrapper">{a}</div>
+                </Tooltip>
+            }
+        },
+        {
+            title: '是否有效',
+            dataIndex: 'enabled',
+            key: 'enabled',
+            align: 'center',
+            width: 60,
+            render: (a: any, b: any) => {
+                return <Badge status={a ? "processing" : "error"} text={a ? '是' : '否'} />
+            }
+        },
+        {
+            title: '授权时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 150
+        },
+        {
+            title: '日限额(分)',
+            dataIndex: 'dailyBudget',
+            key: 'dailyBudget',
+            align: 'center',
+            width: 80
+        },
+        {
+            title: '资金状态',
+            dataIndex: 'fundStatus',
+            key: 'fundStatus',
+            align: 'center',
+            width: 80,
+            render: (a: string | number) => {
+                return FundStatusEnum[a as keyof typeof FundStatusEnum]
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 300,
+            fixed: 'right',
+            render: (a: any, b: any) => {
+                return <Space>
+                    {(Object.keys(b)?.includes('addV3') && !b.addV3 && !b?.adUnitAccount) && <OpenV3 accountId={b?.accountId} onChange={v3Change} />}
+                    <OpenV3All adUnitAccount={b?.adUnitAccount} accountId={b?.accountId} onChange={v3Change} />
+                    <Tooltip title="配置服务商">
+                        <Button size="small" style={{ color: "#00bcd4" }} onClick={() => setOpenServer([b])} icon={<SettingOutlined />}></Button>
+                    </Tooltip>
+                    <Tooltip title="备注">
+                        <Button size="small" style={{ color: '#52c41a' }} onClick={() => edit(b)} icon={<EditOutlined />}></Button>
+                    </Tooltip>
+                    <Tooltip title="切号">
+                        <Button size="small" style={{ color: '#ea5506' }} onClick={() => checkAccount([b])} icon={<SwapOutlined />}></Button>
+                    </Tooltip>
+                    <Tooltip title="变更记录">
+                        <Button size="small" style={{ color: '#4d5aaf' }} onClick={() => changeRecord(b?.accountId, b?.id)} icon={<FileSearchOutlined />}></Button>
+                    </Tooltip>
+                    {activeKey === '1' && <>
+                        {userId == b?.putUserInfo?.userId?.toString() && <Tooltip title="指派投放助理">
+                            <Button size="small" style={{ color: '#0eb83a' }} onClick={() => putUserHandle([b])} icon={<UserSwitchOutlined />}></Button>
+                        </Tooltip>}
+                        {!groupId && <DivideIntoGroups groupIds={b?.groupIds} getAdAccountList={getAdAccountList} accountId={b?.accountId} />}
+                    </>}
+                    {groupId && <Popconfirm
+                        title={`是否把${b?.accountId}移出该分组?`}
+                        onConfirm={() => { del(groupId, b?.accountId) }}
+                        okText="是"
+                        cancelText="否"
+                    >
+                        <Tooltip title="移出分组">
+                            <Button size="small" style={{ color: 'red' }}><UploadOutlined /></Button>
+                        </Tooltip>
+                    </Popconfirm>}
+                </Space>
+            }
+        },
+    ]
+}

+ 78 - 0
src/pages/launchSystemV3/account/tablesConfig.tsx

@@ -0,0 +1,78 @@
+import { EditOutlined } from "@ant-design/icons"
+import { Button, Col, Row } from "antd"
+import React from "react"
+
+// 变更记录
+const columnsCrGdt = (editHandle: (data: any) => void) => {
+    return [
+        {
+            title: '开始时间',
+            dataIndex: 'beginTime',
+            key: 'beginTime',
+            align: 'center',
+            width: 140,
+            fixed: 'left',
+        },
+        {
+            title: '结束时间',
+            dataIndex: 'endTime',
+            key: 'endTime',
+            align: 'center',
+            fixed: 'left',
+            width: 140
+        },
+        {
+            title: '操作人',
+            dataIndex: 'createName',
+            key: 'createName',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return <span>{a || '<空>'}</span>
+            }
+        },
+        {
+            title: '资源名称',
+            dataIndex: 'putResourceName',
+            key: 'putResourceName',
+            width: 140,
+            ellipsis: true,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span>{a || '<空>'}</span>
+            }
+        },
+        {
+            title: '资源标识',
+            dataIndex: 'putResourceKey',
+            key: 'putResourceKey',
+            ellipsis: true,
+            width: 180,
+            render: (a: any, b: any) => {
+                return <span>{a || '<空>'}</span>
+            }
+        },
+        {
+            title: '授权者',
+            dataIndex: 'putUserName',
+            key: 'putUserName',
+            ellipsis: true,
+            width: 80
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            render: (a: string, b: any) => (
+                <Row gutter={[10, 0]}>
+                    <Col><Button type="link" size="small" style={{ fontSize: 12 }} icon={<EditOutlined />} onClick={() => { editHandle(b) }}>编辑</Button></Col>
+                </Row>
+            )
+        }
+    ]
+}
+
+export {
+    columnsCrGdt
+}

+ 5 - 16
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -68,22 +68,11 @@ const Create: React.FC = () => {
 
 
     // 判断游戏还是小说短剧
     // 判断游戏还是小说短剧
     useEffect(() => {
     useEffect(() => {
-        if (initialState?.menu?.data?.length) {
-            const menu = initialState?.menu?.data
-            const ADLAUNCH3 = menu.find((item: { path: string }) => item.path === "/launchSystemV3")
-            const ADLAUNCH2 = menu.find((item: { path: string }) => item.path === "/launchSystemNew")
-            const ACCOUNTLIST = ADLAUNCH3?.routes?.find((item: { path: string }) => item.path === "/launchSystemV3/account") || ADLAUNCH2?.routes?.find((item: { path: string }) => item.path === "/launchSystemNew/account")
-            let newputInTypeList: { label: string, value: string }[] = []
-            ACCOUNTLIST?.routes.forEach((item: { path: string }) => {
-                if (item.path.includes('novel')) {
-                    newputInTypeList.push({ label: '小说/短剧投放', value: 'NOVEL' })
-                } else if (item.path.includes('game')) {
-                    newputInTypeList.push({ label: '游戏投放', value: 'GAME' })
-                }
-            })
-            setPutInTypeList(newputInTypeList);
-            newputInTypeList.length > 0 && setPutInType(type => (type && newputInTypeList.some(item => item.value === type) ? type : newputInTypeList[0].value) as 'NOVEL' | 'GAME');
-        }
+        let newputInTypeList: { label: string, value: string }[] = []
+        newputInTypeList.push({ label: '小说/短剧投放', value: 'NOVEL' })
+        newputInTypeList.push({ label: '游戏投放', value: 'GAME' })
+        setPutInTypeList(newputInTypeList);
+        newputInTypeList.length > 0 && setPutInType(type => (type && newputInTypeList.some(item => item.value === type) ? type : newputInTypeList[0].value) as 'NOVEL' | 'GAME');
     }, [initialState?.menu])
     }, [initialState?.menu])
 
 
     useEffect(() => {
     useEffect(() => {

+ 3 - 1
src/services/launchAdq/adAuthorize.ts

@@ -8,6 +8,7 @@ import { api } from '../api'
 export interface GetAdAccountParams {
 export interface GetAdAccountParams {
     pageNum: number,
     pageNum: number,
     pageSize: number,
     pageSize: number,
+    appType: 1 | 2 | 3, // 1小说 2游戏 3短剧
     groupId?: number,
     groupId?: number,
     accountIds?: string[] | string
     accountIds?: string[] | string
     putUserId?: number,
     putUserId?: number,
@@ -16,7 +17,8 @@ export interface GetAdAccountParams {
     adUnitType?: string
     adUnitType?: string
 }
 }
 export async function getAdAccountListApi(data: GetAdAccountParams) {
 export async function getAdAccountListApi(data: GetAdAccountParams) {
-    return request(api + '/adq/adAccount/accountListOfNovel', {
+    // return request(api + '/adq/adAccount/accountListOfNovel', {
+    return request(api + '/adq/adAccount/accountListOfType', {
         method: 'POST',
         method: 'POST',
         data
         data
     });
     });