Parcourir la source

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

wjx il y a 1 semaine
Parent
commit
07f593f874

+ 1 - 1
src/components/SelectAdAccount/index.tsx

@@ -64,7 +64,7 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
             getUserAccountList.run(params).then(res => {
                 if (isReturnFirstValue && !isFirstReturn) {
                     setIsFirstReturn(() => true)
-                    if (res?.records?.length) {
+                    if (res?.records?.length && !value || value?.length === 0) {
                         onChange?.(res.records[0].accountId, res.records[0])
                     }
                 }

+ 170 - 0
src/pages/launchSystemV3/components/Audience/index.tsx

@@ -0,0 +1,170 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, message, Modal, Radio, Space, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from './tableConfig'
+import { getAudiencesListApi } from "@/services/adqV3/global"
+
+/**
+ * 优量汇流量包
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const Audience: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange } = props
+    const [tableData, setTableData] = useState<any[]>([])//table数据
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+    const [positionPackageType, setPositionPackageType] = useState<'customAudience' | 'excludedCustomAudience'>('customAudience')
+
+    const getAudiencesList = useAjax((params) => getAudiencesListApi(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList(data[selectAdz - 1].accountId)
+        } else {
+            setTableData([])
+        }
+    }, [selectAdz])
+
+    // 获取人群包
+    const getList = (accountId: number) => {
+        getAudiencesList.run({ adAccountId: accountId }).then(res => {
+            setTableData(res || [])
+        })
+    }
+
+    const handleOk = () => {
+        onChange?.(data)
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (selectedRowKeys: React.Key[]) => {
+        const newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1][positionPackageType] = selectedRowKeys
+        setData([...newData])
+    }
+
+    // 清空已选
+    const clearGoods = () => {
+        const newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1][positionPackageType] = []
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        const newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        const audienceIds: string[] = data[selectAdz - 1][positionPackageType] || []
+        const fileterData = newData.filter(item => item.accountId !== data[selectAdz - 1].accountId)
+        const ajax = fileterData.map(item => getAudiencesListApi({ adAccountId: item.accountId }))
+        Promise.all(ajax).then(res => {
+            fileterData.forEach((item, index) => {
+                const dataAudienceIds = res[index]?.data?.map((i: { audienceId: any }) => i.audienceId) || []
+                const newDataIndex = newData.findIndex(d => d.accountId === item.accountId)
+                if (newDataIndex !== -1) {
+                    newData[newDataIndex][positionPackageType] = audienceIds.filter(a => dataAudienceIds.includes(a))
+                }
+            })
+            setData([...newData])
+            hide()
+            message.success('设置成功');
+        })
+    }
+
+
+    return <Modal
+        title={<strong>选择自定义人群包</strong>}
+        open={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={900}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        const positionPackage = data[index]?.[positionPackageType] || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {positionPackage?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="center">
+                    <Radio.Group value={positionPackageType} onChange={(e) => setPositionPackageType(e.target.value)} buttonStyle="solid">
+                        <Radio.Button value="customAudience">定向用户群 {(data[selectAdz - 1]?.['customAudience'] || [])?.length > 0 && <CheckOutlined />}</Radio.Button>
+                        <Radio.Button value="excludedCustomAudience">排除用户群 {(data[selectAdz - 1]?.['excludedCustomAudience'] || [])?.length > 0 && <CheckOutlined />}</Radio.Button>
+                    </Radio.Group>
+                    <Button icon={<SyncOutlined />} type='link' style={{ padding: 0 }} loading={getAudiencesList?.loading} onClick={() => { getList(data[selectAdz - 1].accountId) }}>刷新</Button>
+                    {data?.length > 1 && <Button style={{ padding: 0 }} disabled={!data[selectAdz - 1][positionPackageType]?.length} onClick={() => setOnekey()} type="link" loading={getAudiencesList.loading}>
+                        <Space>
+                            <span>一键设置{positionPackageType === 'customAudience' ? '定向用户群' : '排除用户群'}</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同ID的人群包为那个账号的人群包(注意需要用户人群包ID相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.[positionPackageType] || [])?.length > 0 && <Button style={{ padding: 0 }} type='link' danger onClick={() => { clearGoods() }}>清空{positionPackageType === 'customAudience' ? '定向用户群' : '排除用户群'}</Button>}
+                </Space>
+                <Table
+                    columns={columns()}
+                    dataSource={tableData}
+                    size="small"
+                    loading={getAudiencesList?.loading}
+                    scroll={{ y: 400 }}
+                    rowKey={'audienceId'}
+                    rowSelection={{
+                        selectedRowKeys: data[selectAdz - 1]?.[positionPackageType],
+                        getCheckboxProps: (record: any) => {
+                            return {
+                                disabled: !['PENDING', 'PROCESSING', 'SUCCESS'].includes(record.status) ||
+                                    data?.[selectAdz - 1]?.[positionPackageType === 'excludedCustomAudience' ? 'customAudience' : 'excludedCustomAudience']?.includes(record.audienceId)
+                            }
+                        },
+                        onChange: onChangeTable
+                    }}
+                    onRow={(record) => ({
+                        onClick: () => {
+                            let newDatas = JSON.parse(JSON.stringify(data))
+                            let oldData = newDatas[selectAdz - 1]?.[positionPackageType] || []
+                            const selected = oldData?.some((item: any) => item === record.audienceId);
+                            const newSelectedRows = selected
+                                ? oldData?.filter((item: any) => item !== record.audienceId)
+                                : [...oldData, record.audienceId];
+                            newDatas[selectAdz - 1][positionPackageType] = newSelectedRows;
+                            setData([...newDatas])
+                        },
+                    })}
+                />
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(Audience)

+ 50 - 0
src/pages/launchSystemV3/components/Audience/tableConfig.tsx

@@ -0,0 +1,50 @@
+import { TableProps } from "antd"
+import React from "react"
+import moment from "moment"
+import { STATUS_ENUM } from "../../tencentAdPutIn/const"
+
+const columns = (): TableProps<any>['columns'] => [
+    {
+        title: '用户群名称',
+        dataIndex: 'name',
+        key: 'name',
+        ellipsis: true,
+        width: 180,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: 'ID',
+        dataIndex: 'audienceId',
+        key: 'audienceId',
+        ellipsis: true,
+        width: 120,
+        align: 'center',
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: '覆盖数量',
+        dataIndex: 'userCount',
+        key: 'userCount',
+        width: 120,
+        align: 'center',
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: '状态',
+        dataIndex: 'status',
+        key: 'status',
+        ellipsis: true,
+        width: 100,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{STATUS_ENUM[value as keyof typeof STATUS_ENUM]}</span>
+        }
+    },
+]
+
+export default columns

+ 49 - 2
src/pages/launchSystemV3/tencenTasset/corpWechat/csgroup/components/group/addCsGroup.tsx

@@ -1,9 +1,10 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import '../../../../../tencentAdPutIn/index.less'
-import { Form, Input, message, Modal, Radio, Select } from 'antd';
+import { Form, Input, InputNumber, message, Modal, Radio, Select } from 'antd';
 import { useAjax } from '@/Hook/useAjax';
 import { addLocalCorpCsgroupApi, modifyLocalCorpCsgroupApi } from '@/services/adqV3/global';
 import { useWatch } from 'antd/lib/form/Form';
+import { getAllUser } from '@/services/operating/account';
 
 interface AddCsGroupProps {
     corpWechatList: { label: string; value: string }[];
@@ -24,8 +25,13 @@ const AddCsGroup: React.FC<AddCsGroupProps> = ({ corpWechatList, userRotatePolic
 
     const addLocalCorpCsgroup = useAjax((params) => addLocalCorpCsgroupApi(params))
     const modifyLocalCorpCsgroup = useAjax((params) => modifyLocalCorpCsgroupApi(params))
+    const getAllUser1 = useAjax(() => getAllUser())
     /**************************************/
 
+    useEffect(() => {
+        getAllUser1.run()
+    }, [])
+
     const handleOk = () => {
         form.validateFields().then(values => {
             console.log(values)
@@ -104,6 +110,47 @@ const AddCsGroup: React.FC<AddCsGroupProps> = ({ corpWechatList, userRotatePolic
                         options={localCorpCsgroupList}
                     />
                 </Form.Item>
+                <Form.Item label={<strong>指派用户</strong>} name="assignUserIdList">
+                    <Select
+                        showSearch
+                        placeholder="请选择用户"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        mode='multiple'
+                        options={getAllUser1.data?.map((user: { nickname: any; userId: any; }) => ({ label: user.nickname, value: user.userId }))}
+                    />
+                </Form.Item>
+                <Form.Item label={<strong>通知用户</strong>} name="callUserIdList">
+                    <Select
+                        showSearch
+                        placeholder="请选择用户"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        mode='multiple'
+                        options={getAllUser1.data?.map((user: { nickname: any; userId: any; }) => ({ label: user.nickname, value: user.userId }))}
+                    />
+                </Form.Item>
+                <Form.Item
+                    label={<strong>在线通知人数</strong>}
+                    name="callOnlineCount"
+                    rules={[
+                        {
+                            validator: (_, value) =>
+                                value === undefined || value === null || Number.isInteger(value)
+                                    ? Promise.resolve()
+                                    : Promise.reject('请输入整数')
+                        }
+                    ]}
+                >
+                    <InputNumber
+                        placeholder='请输入在线通知人数'
+                        style={{ width: '100%' }}
+                    />
+                </Form.Item>
             </>}
             {(initialValues?.id ? !initialValues?.parentId : !parentId) && <Form.Item label={<strong>切号策略</strong>} name="policyId">
                 <Select

+ 5 - 1
src/pages/launchSystemV3/tencenTasset/corpWechat/csgroup/components/group/index.tsx

@@ -54,7 +54,11 @@ const Group: React.FC<{ adAccountId?: number }> = ({ adAccountId }) => {
     }, [queryParamsNew])
 
     const handleEdit = (data: any) => {
-        setInitialValues(data)
+        setInitialValues({
+            ...data,
+            assignUserIdList: data?.assignUserList?.map((item: { userId: number }) => item.userId) || [],
+            callUserIdList: data?.callUserList?.map((item: { userId: number }) => item.userId) || []
+        })
         setVisible(true);
     }
 

+ 31 - 1
src/pages/launchSystemV3/tencenTasset/corpWechat/csgroup/components/group/tableConfig.tsx

@@ -77,6 +77,36 @@ const columns = (handleEdit: (data: any) => void): TableProps<any>['columns'] =>
                 return <span style={{ fontSize: 12 }}>{value}</span>
             }
         },
+        {
+            title: '在线通知人数',
+            dataIndex: 'callOnlineCount',
+            key: 'callOnlineCount',
+            width: 70,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '指派用户',
+            dataIndex: 'assignUserList',
+            key: 'assignUserList',
+            width: 120,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value?.length > 0 ? value?.map((item: { userName: any }) => item.userName) : '--'}</span>
+            }
+        },
+        {
+            title: '通知用户',
+            dataIndex: 'callUserList',
+            key: 'callUserList',
+            width: 120,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value?.length > 0 ? value?.map((item: { userName: any }) => item.userName) : '--'}</span>
+            }
+        },
         {
             title: '创建时间',
             dataIndex: 'createTime',
@@ -104,7 +134,7 @@ const columns = (handleEdit: (data: any) => void): TableProps<any>['columns'] =>
             render(_, record) {
                 return <Space>
                     {/* <UserInfo userInfoList={record.userInfoList} createTime={record.createTime} groupMemberCnt={record.groupMemberCnt} /> */}
-                    {record.type == 1 && <GroupUserInfo adAccountId={record.accountId} groupData={record} />} 
+                    {record.type == 1 && <GroupUserInfo adAccountId={record.accountId} groupData={record} />}
                     <Button icon={<EditOutlined />} style={{ border: 'none', fontSize: 12 }} size='small' onClick={() => handleEdit(record)}>修改</Button>
                 </Space>
             }

+ 4 - 3
src/pages/launchSystemV3/tencenTasset/corpWechat/csgroup/index.tsx

@@ -14,7 +14,8 @@ import TencentGroup from './components/tencentGroup';
 const EnterpriseWechat: React.FC = () => {
 
     /**********************************/
-    const [adAccountId, setAdAccountId] = React.useState<number>();
+    const accountStr = localStorage.getItem('ADQ_ACCOUNTID')
+    const [adAccountId, setAdAccountId] = React.useState<number | undefined>(accountStr ? Number(accountStr) : undefined);
     const [accessKey, setAccessKey] = React.useState<string>('1');
     /**********************************/
 
@@ -26,9 +27,9 @@ const EnterpriseWechat: React.FC = () => {
                 isReturnFirstValue
                 value={adAccountId ? [adAccountId] : undefined}
                 type="radio"
-                onChange={(value, row) => {
-                    console.log(row)
+                onChange={(value) => {
                     setAdAccountId(value as number)
+                    localStorage.setItem('ADQ_ACCOUNTID', String(value))
                 }}
                 allowClear={false}
             />

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

@@ -855,4 +855,16 @@ export enum APPEAL_STATUS_ENUM {
 	COMPLETED = '申诉复审完成',
 	PENDING = '申诉复审待审',
 	CANCEL = '申诉复审取消'
+}
+
+
+/** 申诉复审状态 */
+export enum STATUS_ENUM {
+	PENDING = '待处理',
+	PROCESSING = '处理中',
+	SUCCESS = '成功可用',
+	ERROR = '错误',
+	FROZEN = '冻结',
+	THAWING = '解冻中',
+	LOCKING = '锁定'
 }

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

@@ -27,6 +27,7 @@ import Save from "./Save"
 import { useLocalStorageState } from "ahooks"
 import SelectAccount from "./SelectAccount"
 import PositionPackage from "../../components/PositionPackage"
+import Audience from "../../components/Audience"
 
 export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
 
@@ -49,6 +50,7 @@ const Create: React.FC = () => {
     const [sourceVisible, setSourceVisible] = useState<boolean>(false) // 选择数据源弹窗控制
     const [conversionVisible, setConversionVisible] = useState<boolean>(false) // 选择转化归因控制
     const [positionPackageVisible, setPositionPackageVisible] = useState<boolean>(false) // 选择优量汇流量包
+    const [audienceVisible, setAudienceVisible] = useState<boolean>(false) // 选择定向人群宝
     const [materialData, setMaterialData] = useState<any>({}) // 素材数据
     const [textData, setTextData] = useState<any>({})
     const [tableData, setTableData] = useState<any>({})
@@ -269,7 +271,7 @@ const Create: React.FC = () => {
 
                             let isConversion = false
                             setAccountCreateLogs(Object.keys(accountIdParamVOMap || {}).map(accountId => {
-                                const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, excludeUnionPositionPackages, unionPositionPackages, conversionInfo, wechatChannelVO, wechatAppletList } = accountIdParamVOMap[accountId]
+                                const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, excludeUnionPositionPackages, unionPositionPackages, conversionInfo, wechatChannelVO, wechatAppletList, customAudience, excludedCustomAudience } = accountIdParamVOMap[accountId]
                                 let data: PULLIN.AccountCreateLogsProps = {
                                     accountId: Number(accountId),
                                     productList: productDTOS
@@ -296,6 +298,12 @@ const Create: React.FC = () => {
                                 if (wechatChannelVO) {
                                     data.videoChannelList = [wechatChannelVO]
                                 }
+                                if (customAudience) {
+                                    data.customAudience = customAudience
+                                }
+                                if (excludedCustomAudience) {
+                                    data.excludedCustomAudience = excludedCustomAudience
+                                }
                                 return data
                             }))
 
@@ -788,7 +796,7 @@ const Create: React.FC = () => {
                         if (l.materialType === 4) {
                             return {
                                 type: l?.componentSubType?.includes('IMAGE_LIST') ? 'image_list' : l?.keyFrameImageUrl ? 'video' : 'image',
-                                valueJson: JSON.stringify({ 
+                                valueJson: JSON.stringify({
                                     componentId: l.id,
                                     componentValue: l
                                 })
@@ -842,7 +850,7 @@ const Create: React.FC = () => {
         }
         const accountIdParamDTOMap: any = {}
         accountCreateLogs.forEach(item => {
-            const { pageList, productList, userActionSetsList, unionPositionPackage, excludeUnionPositionPackage, accountId, wechatChannelList, newConversionList, videoChannelList } = item
+            const { pageList, productList, userActionSetsList, unionPositionPackage, excludeUnionPositionPackage, accountId, wechatChannelList, newConversionList, videoChannelList, customAudience, excludedCustomAudience } = item
 
             const userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
 
@@ -868,6 +876,12 @@ const Create: React.FC = () => {
             if (videoChannelList && creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_CHANNELS_PROFILE') {
                 map.videoChannelId = videoChannelList?.[0]?.wechatChannelsAccountId
             }
+            if (customAudience?.length) {
+                map.customAudience = customAudience
+            }
+            if (excludedCustomAudience?.length) {
+                map.excludedCustomAudience = excludedCustomAudience
+            }
 
             accountIdParamDTOMap[accountId] = map
         })
@@ -993,6 +1007,7 @@ const Create: React.FC = () => {
                             <Button type="primary" danger={!accountCreateLogs?.some(item => item?.newConversionList?.length)} onClick={() => { setConversionVisible(true) }}>{accountCreateLogs?.some(item => item?.newConversionList?.length) ? <>重新选择转化归因<CheckOutlined style={{ color: '#FFF' }} /></> : '请选择转化归因'}</Button>
                         }
                         {(siteSet?.includes('SITE_SET_MOBILE_UNION') || automaticSiteEnabled) && <Button onClick={() => { setPositionPackageVisible(true) }}>优量汇流量包(选填){accountCreateLogs?.some(item => item?.unionPositionPackage?.length || item?.excludeUnionPositionPackage?.length) && <CheckOutlined style={{ color: accountCreateLogs?.every(item => item?.unionPositionPackage?.length && item?.excludeUnionPositionPackage?.length) ? '#1890ff' : '#52C41A' }} />}</Button>}
+                        <Button onClick={() => { setAudienceVisible(true) }}>自定义人群包(选填){accountCreateLogs?.some(item => item?.customAudience?.length || item?.excludedCustomAudience?.length) && <CheckOutlined style={{ color: accountCreateLogs?.every(item => item?.customAudience?.length && item?.excludedCustomAudience?.length) ? '#1890ff' : '#52C41A' }} />}</Button>
                     </>}
                 </Space>
 
@@ -1123,6 +1138,18 @@ const Create: React.FC = () => {
                         clearData()
                     }}
                 />}
+                {/* 自定义人群包 */}
+                {audienceVisible && <Audience
+                    visible={audienceVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setAudienceVisible(false)}
+                    onChange={(e) => {
+                        setIsDqSubmit(false)
+                        setAccountCreateLogs(e);
+                        setAudienceVisible(false);
+                        clearData()
+                    }}
+                />}
             </Card>
         </Spin>
 

+ 3 - 1
src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts

@@ -85,7 +85,9 @@ declare namespace PULLIN {
         newConversionList?: any[],
         videoChannelList?: any[]
         unionPositionPackage?: any[]
-        excludeUnionPositionPackage?: any[]
+        excludeUnionPositionPackage?: any[],
+        customAudience?: any[],
+        excludedCustomAudience?: any[]
     }
     interface DynamicReactContent {
         form: FormInstance<any>

+ 11 - 0
src/services/adqV3/global.ts

@@ -203,6 +203,17 @@ export async function getBatchUnionPositionPackagesApi(data: { unionPackageType:
     });
 }
 
+/**
+ * 获取人群包
+ * @param params 
+ * @returns 
+ */
+export async function getAudiencesListApi(params: { adAccountId: number, audienceId?: number }) {
+    return request(api + `/adq/v3/custom/audiences/get`, {
+        method: 'GET',
+        params
+    });
+}
 
 /**
  * 获取创意规格列表