|
@@ -21,9 +21,10 @@ interface Props {
|
|
|
visible?: boolean
|
|
visible?: boolean
|
|
|
onClose?: () => void
|
|
onClose?: () => void
|
|
|
onChange?: (targeting?: any) => void
|
|
onChange?: (targeting?: any) => void
|
|
|
|
|
+ targetRules?: PULLIN.TargetingRulesProps
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onChange, onClose }) => {
|
|
|
|
|
|
|
+const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onChange, onClose, targetRules }) => {
|
|
|
|
|
|
|
|
/******************************/
|
|
/******************************/
|
|
|
const [form] = Form.useForm();
|
|
const [form] = Form.useForm();
|
|
@@ -33,8 +34,6 @@ const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onCh
|
|
|
const wechatAdBehaviorType = Form.useWatch('wechatAdBehaviorType', form);
|
|
const wechatAdBehaviorType = Form.useWatch('wechatAdBehaviorType', form);
|
|
|
const userOsType = Form.useWatch('userOsType', form);
|
|
const userOsType = Form.useWatch('userOsType', form);
|
|
|
const ageType = Form.useWatch('ageType', form);
|
|
const ageType = Form.useWatch('ageType', form);
|
|
|
- const min = Form.useWatch(['age', 'min'], form);
|
|
|
|
|
- const max = Form.useWatch(['age', 'max'], form);
|
|
|
|
|
const age = Form.useWatch('age', form);
|
|
const age = Form.useWatch('age', form);
|
|
|
const education = Form.useWatch('education', form);
|
|
const education = Form.useWatch('education', form);
|
|
|
const networkType = Form.useWatch('networkType', form);
|
|
const networkType = Form.useWatch('networkType', form);
|
|
@@ -410,6 +409,11 @@ const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onCh
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 智能投放 判断是否展示可添加定向
|
|
|
|
|
+ const isShow = (field: string) => {
|
|
|
|
|
+ return (targetRules?.rules && targetRules?.rules?.length > 0) ? targetRules?.rules?.includes(field) : true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return <Modal
|
|
return <Modal
|
|
|
title={isBackVal ? <strong style={{ fontSize: 20 }}>
|
|
title={isBackVal ? <strong style={{ fontSize: 20 }}>
|
|
|
{(value && Object.keys(value).length > 0) ? `修改定向` : `新增定向`}
|
|
{(value && Object.keys(value).length > 0) ? `修改定向` : `新增定向`}
|
|
@@ -419,604 +423,605 @@ const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onCh
|
|
|
footer={null}
|
|
footer={null}
|
|
|
width={920}
|
|
width={920}
|
|
|
className={`modalResetCss`}
|
|
className={`modalResetCss`}
|
|
|
- bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
|
|
|
|
|
|
|
+ bodyStyle={{ padding: 0, position: 'relative', borderRadius: '0 0 8px 8px' }}
|
|
|
maskClosable={false}
|
|
maskClosable={false}
|
|
|
>
|
|
>
|
|
|
- {getTargetingGags.loading && <div style={{ position: 'absolute', width: '100%', top: 0, left: 0, zIndex: 20 }}>
|
|
|
|
|
- <Spin spinning={getTargetingGags.loading}>
|
|
|
|
|
- <div style={{ width: '100%', height: 600, backgroundColor: 'rgba(255,255,255,0.95)' }}></div>
|
|
|
|
|
- </Spin>
|
|
|
|
|
- </div>}
|
|
|
|
|
- <Form
|
|
|
|
|
- form={form}
|
|
|
|
|
- name="newAdTarget"
|
|
|
|
|
- labelAlign='left'
|
|
|
|
|
- labelCol={{ span: 4 }}
|
|
|
|
|
- colon={false}
|
|
|
|
|
- style={{ backgroundColor: '#f1f4fc', maxHeight: 600, overflow: 'hidden', overflowY: 'auto', padding: '10px 10px 10px', borderRadius: '0 0 8px 8px' }}
|
|
|
|
|
- scrollToFirstError
|
|
|
|
|
- onFinishFailed={({ errorFields }) => {
|
|
|
|
|
- message.error(errorFields?.[0]?.errors?.[0])
|
|
|
|
|
- }}
|
|
|
|
|
- onFinish={handleOk}
|
|
|
|
|
- initialValues={{
|
|
|
|
|
- taskType: 'NOVEL',
|
|
|
|
|
- geoLocationType: '0',
|
|
|
|
|
- maritalStatusType: '0',
|
|
|
|
|
- deviceBrandModelType: '0',
|
|
|
|
|
- userOsType: '0',
|
|
|
|
|
- ageType: '0',
|
|
|
|
|
- wechatAdBehaviorType: ['0'],
|
|
|
|
|
- age: [{}],
|
|
|
|
|
- gender: '0',
|
|
|
|
|
- education: ['0'],
|
|
|
|
|
- networkType: ['0'],
|
|
|
|
|
- devicePrice: ['0'],
|
|
|
|
|
- gameConsumptionLevel: ['0'],
|
|
|
|
|
- excludedConvertedAudience: {
|
|
|
|
|
- excludedDimension: '0'
|
|
|
|
|
- },
|
|
|
|
|
- conversionBehaviorType: 'optimization',
|
|
|
|
|
- geoLocation: {
|
|
|
|
|
- locationTypes: ['LIVE_IN']
|
|
|
|
|
- },
|
|
|
|
|
- targetingName: (isBackVal ? '定向' : '定向模板') + '_' + localStorage.getItem('userId') + '_' + moment().format('MM_DD_HH:mm:ss')
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- {!putInType && <Card
|
|
|
|
|
- title={<strong style={{ fontSize: 18 }}>定向类型</strong>}
|
|
|
|
|
- className="cardResetCss newCss"
|
|
|
|
|
- bodyStyle={{ padding: '4px 6px' }}
|
|
|
|
|
- style={{ marginBottom: 8 }}
|
|
|
|
|
- >
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="taskType" label={<strong>投放类型</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择定向类型' }]}>
|
|
|
|
|
- <Radio.Group onChange={(e) => {
|
|
|
|
|
- form.setFieldsValue({
|
|
|
|
|
- excludedConvertedAudience: {
|
|
|
|
|
- excludedDimension: '0'
|
|
|
|
|
- },
|
|
|
|
|
- userOsType: '0',
|
|
|
|
|
- wechatAdBehaviorType: ['0']
|
|
|
|
|
- })
|
|
|
|
|
- }}>
|
|
|
|
|
- <Radio value="NOVEL">小说</Radio>
|
|
|
|
|
- <Radio value="GAME">游戏</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
- </Card>}
|
|
|
|
|
- <Card
|
|
|
|
|
- title={<strong style={{ fontSize: 18 }}>定向选择</strong>}
|
|
|
|
|
- className="cardResetCss newCss"
|
|
|
|
|
- bodyStyle={{ padding: '4px 6px' }}
|
|
|
|
|
- style={{ marginBottom: 8 }}
|
|
|
|
|
- >
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="geoLocationType" label={<strong>地理位置</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- <Radio value="1">按区域</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {geoLocationType === '1' && <div className={style.newSpace_bottom}>
|
|
|
|
|
- {/* <Title level={5} style={{ fontSize: 14 }}>微信流量(除视频号/搜一搜)暂时仅支持 “常住地”或“旅行到访”。按法务合规要求,“常住地”暂不支持国内港澳台及国外地区,“旅行到访”仅支持部分国外地区。</Title> */}
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name={['geoLocation', 'regions']}
|
|
|
|
|
- rules={[
|
|
|
|
|
- { required: true, message: '请选择区域' },
|
|
|
|
|
- { type: 'array', max: 1000, message: '地理位置最多选择1000' },
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- <TreeSelect
|
|
|
|
|
- placeholder="请选择"
|
|
|
|
|
- showSearch={true}
|
|
|
|
|
- maxTagCount={50}
|
|
|
|
|
- treeCheckable={true}
|
|
|
|
|
- showCheckedStrategy={TreeSelect.SHOW_PARENT}
|
|
|
|
|
- treeData={regionsList}
|
|
|
|
|
- loading={getTargetingGags.loading}
|
|
|
|
|
- style={{ width: '100%' }}
|
|
|
|
|
- allowClear
|
|
|
|
|
- filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
- if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
- return true
|
|
|
|
|
- } else {
|
|
|
|
|
- return false
|
|
|
|
|
|
|
+ <Spin spinning={getTargetingGags.loading}>
|
|
|
|
|
+ <div style={{ width: '100%', position: 'relative', padding: '0 0 40px' }}>
|
|
|
|
|
+ <Form
|
|
|
|
|
+ form={form}
|
|
|
|
|
+ name="newAdTarget"
|
|
|
|
|
+ labelAlign='left'
|
|
|
|
|
+ labelCol={{ span: 4 }}
|
|
|
|
|
+ colon={false}
|
|
|
|
|
+ style={{ backgroundColor: '#f1f4fc', maxHeight: 600, overflow: 'hidden', overflowY: 'auto', padding: '10px 10px 10px', borderRadius: '0 0 8px 8px' }}
|
|
|
|
|
+ scrollToFirstError
|
|
|
|
|
+ onFinishFailed={({ errorFields }) => {
|
|
|
|
|
+ message.error(errorFields?.[0]?.errors?.[0])
|
|
|
|
|
+ }}
|
|
|
|
|
+ onFinish={handleOk}
|
|
|
|
|
+ initialValues={{
|
|
|
|
|
+ taskType: 'NOVEL',
|
|
|
|
|
+ geoLocationType: '0',
|
|
|
|
|
+ maritalStatusType: '0',
|
|
|
|
|
+ deviceBrandModelType: '0',
|
|
|
|
|
+ userOsType: '0',
|
|
|
|
|
+ ageType: '0',
|
|
|
|
|
+ wechatAdBehaviorType: ['0'],
|
|
|
|
|
+ age: [{}],
|
|
|
|
|
+ gender: '0',
|
|
|
|
|
+ education: ['0'],
|
|
|
|
|
+ networkType: ['0'],
|
|
|
|
|
+ devicePrice: ['0'],
|
|
|
|
|
+ gameConsumptionLevel: ['0'],
|
|
|
|
|
+ excludedConvertedAudience: {
|
|
|
|
|
+ excludedDimension: '0'
|
|
|
|
|
+ },
|
|
|
|
|
+ conversionBehaviorType: 'optimization',
|
|
|
|
|
+ geoLocation: {
|
|
|
|
|
+ locationTypes: ['LIVE_IN']
|
|
|
|
|
+ },
|
|
|
|
|
+ targetingName: (isBackVal ? '定向' : '定向模板') + '_' + localStorage.getItem('userId') + '_' + moment().format('MM_DD_HH:mm:ss')
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {!putInType && <Card
|
|
|
|
|
+ title={<strong style={{ fontSize: 18 }}>定向类型</strong>}
|
|
|
|
|
+ className="cardResetCss newCss"
|
|
|
|
|
+ bodyStyle={{ padding: '4px 6px' }}
|
|
|
|
|
+ style={{ marginBottom: 8 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item name="taskType" label={<strong>投放类型</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择定向类型' }]}>
|
|
|
|
|
+ <Radio.Group onChange={(e) => {
|
|
|
|
|
+ form.setFieldsValue({
|
|
|
|
|
+ excludedConvertedAudience: {
|
|
|
|
|
+ excludedDimension: '0'
|
|
|
|
|
+ },
|
|
|
|
|
+ userOsType: '0',
|
|
|
|
|
+ wechatAdBehaviorType: ['0']
|
|
|
|
|
+ })
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <Radio value="NOVEL">小说</Radio>
|
|
|
|
|
+ <Radio value="GAME">游戏</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Card>}
|
|
|
|
|
+ <Card
|
|
|
|
|
+ title={<strong style={{ fontSize: 18 }}>定向选择</strong>}
|
|
|
|
|
+ className="cardResetCss newCss"
|
|
|
|
|
+ bodyStyle={{ padding: '4px 6px' }}
|
|
|
|
|
+ style={{ marginBottom: 8 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {isShow('geoLocation') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item name="geoLocationType" label={<strong>地理位置</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ <Radio value="1">按区域</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {geoLocationType === '1' && <div className={style.newSpace_bottom}>
|
|
|
|
|
+ {/* <Title level={5} style={{ fontSize: 14 }}>微信流量(除视频号/搜一搜)暂时仅支持 “常住地”或“旅行到访”。按法务合规要求,“常住地”暂不支持国内港澳台及国外地区,“旅行到访”仅支持部分国外地区。</Title> */}
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name={['geoLocation', 'regions']}
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请选择区域' },
|
|
|
|
|
+ { type: 'array', max: 1000, message: '地理位置最多选择1000' },
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <TreeSelect
|
|
|
|
|
+ placeholder="请选择"
|
|
|
|
|
+ showSearch={true}
|
|
|
|
|
+ maxTagCount={50}
|
|
|
|
|
+ treeCheckable={true}
|
|
|
|
|
+ showCheckedStrategy={TreeSelect.SHOW_PARENT}
|
|
|
|
|
+ treeData={regionsList}
|
|
|
|
|
+ loading={getTargetingGags.loading}
|
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
+ if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name={['geoLocation', 'locationTypes']}
|
|
|
|
|
+ rules={[{ required: true, message: '请选择地点类型' }]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ disabled
|
|
|
|
|
+ options={Object.keys(LOCATION_TYPES_ENUM)?.map(key => ({ label: LOCATION_TYPES_ENUM[key as keyof typeof LOCATION_TYPES_ENUM], value: key }))}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </div>}
|
|
|
|
|
+
|
|
|
|
|
+ {isShow('age') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item name="ageType" label={<strong>年龄</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ <Radio value="14_18">14~18岁</Radio>
|
|
|
|
|
+ <Radio value="19_24">19~24岁</Radio>
|
|
|
|
|
+ <Radio value="25_29">25~29岁</Radio>
|
|
|
|
|
+ <Radio value="30_39">30~39岁</Radio>
|
|
|
|
|
+ <Radio value="40_49">40~49岁</Radio>
|
|
|
|
|
+ <Radio value="50_66">50岁及以上</Radio>
|
|
|
|
|
+ <Radio value="1">自定义</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {ageType === '1' && <Form.List
|
|
|
|
|
+ name='age'
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (_, ranges) => {
|
|
|
|
|
+ if (hasOverlap(ranges)) {
|
|
|
|
|
+ return Promise.reject('年龄存在区间重叠,请修改');
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.resolve();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ {(fields, { add, remove }) => {
|
|
|
|
|
+ return <>
|
|
|
|
|
+ {fields.map(({ key, name, ...restField }, index) => {
|
|
|
|
|
+ const ag = age?.[index]
|
|
|
|
|
+ return <div className={`${style.newSpace_bottom} flexStart`} key={key} style={{ '--g': '5px', padding: '10px 16px' } as React.CSSProperties}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ {...restField}
|
|
|
|
|
+ name={[name, 'min']}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请选择最小年龄!' },
|
|
|
|
|
+ ({ getFieldValue }) => ({
|
|
|
|
|
+ validator(_, min) {
|
|
|
|
|
+ const max = getFieldValue(['age', name, 'max']);
|
|
|
|
|
+ if (min && max && max - min < 4) {
|
|
|
|
|
+ return Promise.reject('年龄跨度需≥4岁');
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.resolve();
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Select style={{ width: 185 }} placeholder="请选择">
|
|
|
|
|
+ {Array(66 - 13).fill('').map((_, i) => i + 14).filter(i => i !== 15 && i !== 16 && i !== 17).map(i => {
|
|
|
|
|
+ return <Select.Option disabled={i > ag?.max} value={i} key={i}>{i === 66 ? '66 岁及以上' : i + ' 岁'}</Select.Option>
|
|
|
|
|
+ })}
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <span>-</span>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ {...restField}
|
|
|
|
|
+ name={[name, 'max']}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请选择最大年龄!' },
|
|
|
|
|
+ ({ getFieldValue }) => ({
|
|
|
|
|
+ validator(_, max) {
|
|
|
|
|
+ const min = getFieldValue(['age', name, 'min']);
|
|
|
|
|
+ if (min && max && max - min < 4) {
|
|
|
|
|
+ return Promise.reject('年龄跨度需≥4岁');
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.resolve();
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Select style={{ width: 185 }} placeholder="请选择">
|
|
|
|
|
+ {Array(66 - 18).fill('').map((_, i) => {
|
|
|
|
|
+ return <Select.Option disabled={i + 19 < ag?.min + 4} value={i + 19} key={i + 19}>{i + 19 === 66 ? '66 岁及以上' : i + 19 + ' 岁'}</Select.Option>
|
|
|
|
|
+ })}
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {age?.length > 1 && <Button danger onClick={() => remove(name)}><DeleteOutlined /></Button>}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ })}
|
|
|
|
|
+ {age?.length < 5 && <Form.Item noStyle>
|
|
|
|
|
+ <Button type="dashed" onClick={() => add()} style={{ width: '100%' }} icon={<PlusOutlined />}>
|
|
|
|
|
+ 新增年龄段
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </Form.Item>}
|
|
|
|
|
+ </>
|
|
|
}}
|
|
}}
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name={['geoLocation', 'locationTypes']}
|
|
|
|
|
- rules={[{ required: true, message: '请选择地点类型' }]}
|
|
|
|
|
- >
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- disabled
|
|
|
|
|
- options={Object.keys(LOCATION_TYPES_ENUM)?.map(key => ({ label: LOCATION_TYPES_ENUM[key as keyof typeof LOCATION_TYPES_ENUM], value: key }))}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </Form.List>}
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="ageType" label={<strong>年龄</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- <Radio value="14_18">14~18岁</Radio>
|
|
|
|
|
- <Radio value="19_24">19~24岁</Radio>
|
|
|
|
|
- <Radio value="25_29">25~29岁</Radio>
|
|
|
|
|
- <Radio value="30_39">30~39岁</Radio>
|
|
|
|
|
- <Radio value="40_49">40~49岁</Radio>
|
|
|
|
|
- <Radio value="50_66">50岁及以上</Radio>
|
|
|
|
|
- <Radio value="1">自定义</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {ageType === '1' && <Form.List
|
|
|
|
|
- name='age'
|
|
|
|
|
- rules={[
|
|
|
|
|
- {
|
|
|
|
|
- validator: (_, ranges) => {
|
|
|
|
|
- if (hasOverlap(ranges)) {
|
|
|
|
|
- return Promise.reject('年龄存在区间重叠,请修改');
|
|
|
|
|
|
|
+ {isShow('gender') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item name="gender" label={<strong>性别</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ {Object.keys(GENDER_ENUM).map(key => {
|
|
|
|
|
+ return <Radio value={key} key={key}>{GENDER_ENUM[key as keyof typeof GENDER_ENUM]}</Radio>
|
|
|
|
|
+ })}
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+
|
|
|
|
|
+ {isShow('education') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="education"
|
|
|
|
|
+ label={<Space>
|
|
|
|
|
+ <strong>学历</strong>
|
|
|
|
|
+ <Tooltip title="用户的最高学历">
|
|
|
|
|
+ <QuestionCircleFilled />
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ </Space>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ getValueFromEvent={(e: string[]) => {
|
|
|
|
|
+ if (e.length > 1 && !education.includes('0') && e.includes('0')) {
|
|
|
|
|
+ return ['0'];
|
|
|
}
|
|
}
|
|
|
- return Promise.resolve();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- {(fields, { add, remove }) => {
|
|
|
|
|
- return <>
|
|
|
|
|
- {fields.map(({ key, name, ...restField }, index) => {
|
|
|
|
|
- const ag = age?.[index]
|
|
|
|
|
- return <div className={`${style.newSpace_bottom} flexStart`} key={key} style={{ '--g': '5px', padding: '10px 16px' } as React.CSSProperties}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- {...restField}
|
|
|
|
|
- name={[name, 'min']}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- rules={[
|
|
|
|
|
- { required: true, message: '请选择最小年龄!' },
|
|
|
|
|
- ({ getFieldValue }) => ({
|
|
|
|
|
- validator(_, min) {
|
|
|
|
|
- const max = getFieldValue(['age', name, 'max']);
|
|
|
|
|
- if (min && max && max - min < 4) {
|
|
|
|
|
- return Promise.reject('年龄跨度需≥4岁');
|
|
|
|
|
- }
|
|
|
|
|
- return Promise.resolve();
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- <Select style={{ width: 185 }} placeholder="请选择">
|
|
|
|
|
- {Array(66 - 13).fill('').map((_, i) => i + 14).filter(i => i !== 15 && i !== 16 && i !== 17).map(i => {
|
|
|
|
|
- return <Select.Option disabled={i > ag?.max} value={i} key={i}>{i === 66 ? '66 岁及以上' : i + ' 岁'}</Select.Option>
|
|
|
|
|
- })}
|
|
|
|
|
- </Select>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- <span>-</span>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- {...restField}
|
|
|
|
|
- name={[name, 'max']}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- rules={[
|
|
|
|
|
- { required: true, message: '请选择最大年龄!' },
|
|
|
|
|
- ({ getFieldValue }) => ({
|
|
|
|
|
- validator(_, max) {
|
|
|
|
|
- const min = getFieldValue(['age', name, 'min']);
|
|
|
|
|
- if (min && max && max - min < 4) {
|
|
|
|
|
- return Promise.reject('年龄跨度需≥4岁');
|
|
|
|
|
- }
|
|
|
|
|
- return Promise.resolve();
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- <Select style={{ width: 185 }} placeholder="请选择">
|
|
|
|
|
- {Array(66 - 17).fill('').map((_, i) => {
|
|
|
|
|
- return <Select.Option disabled={i + 18 < ag?.min + 4} value={i + 18} key={i + 18}>{i + 18 === 66 ? '66 岁及以上' : i + 18 + ' 岁'}</Select.Option>
|
|
|
|
|
- })}
|
|
|
|
|
- </Select>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {age?.length > 1 && <Button danger onClick={() => remove(name)}><DeleteOutlined /></Button>}
|
|
|
|
|
- </div>
|
|
|
|
|
- })}
|
|
|
|
|
- {age?.length < 5 && <Form.Item noStyle>
|
|
|
|
|
- <Button type="dashed" onClick={() => add()} style={{ width: '100%' }} icon={<PlusOutlined />}>
|
|
|
|
|
- 新增年龄段
|
|
|
|
|
- </Button>
|
|
|
|
|
- </Form.Item>}
|
|
|
|
|
- </>
|
|
|
|
|
- }}
|
|
|
|
|
- </Form.List>}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' },
|
|
|
|
|
+ ...Object.keys(EDUCATION_ENUM)?.map(key => ({ label: EDUCATION_ENUM[key as keyof typeof EDUCATION_ENUM], value: key }))
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="gender" label={<strong>性别</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- {Object.keys(GENDER_ENUM).map(key => {
|
|
|
|
|
- return <Radio value={key} key={key}>{GENDER_ENUM[key as keyof typeof GENDER_ENUM]}</Radio>
|
|
|
|
|
- })}
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {isShow('networkType') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="networkType"
|
|
|
|
|
+ label={<strong>联网方式</strong>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ getValueFromEvent={(e: string[]) => {
|
|
|
|
|
+ if (e.length > 1 && !networkType.includes('0') && e.includes('0')) {
|
|
|
|
|
+ return ['0'];
|
|
|
|
|
+ }
|
|
|
|
|
+ return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' },
|
|
|
|
|
+ ...Object.keys(NETWORK_ENUM)?.map(key => ({ label: NETWORK_ENUM[key as keyof typeof NETWORK_ENUM], value: key }))
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="education"
|
|
|
|
|
- label={<Space>
|
|
|
|
|
- <strong>学历</strong>
|
|
|
|
|
- <Tooltip title="用户的最高学历">
|
|
|
|
|
- <QuestionCircleFilled />
|
|
|
|
|
- </Tooltip>
|
|
|
|
|
- </Space>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- getValueFromEvent={(e: string[]) => {
|
|
|
|
|
- if (e.length > 1 && !education.includes('0') && e.includes('0')) {
|
|
|
|
|
- return ['0'];
|
|
|
|
|
- }
|
|
|
|
|
- return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' },
|
|
|
|
|
- ...Object.keys(EDUCATION_ENUM)?.map(key => ({ label: EDUCATION_ENUM[key as keyof typeof EDUCATION_ENUM], value: key }))
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {(isShow('excludedCustomAudience')) && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label={<Space>
|
|
|
|
|
+ <strong>自定义人群</strong>
|
|
|
|
|
+ <Tooltip title={<span>
|
|
|
|
|
+ 自定义人群是指客户通过腾讯广告知数(原DMP)创建和管理自己定义类人群,包括您自行上传的号码包人群等。仅当出价方式选择CPC、 CPM或oCPM(且优化目标为“点击”)时,你可以“二方人群”进行投放。
|
|
|
|
|
+ <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3161&pid=8995" target="__blank">了解更多</a>
|
|
|
|
|
+ </span>}>
|
|
|
|
|
+ <QuestionCircleFilled />
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ </Space>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Space>
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ defaultValue={['0']}
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ <span style={{ color: '#FAAD14' }}>自定义人群必须关联指定账户,当前关联账户为不限</span>
|
|
|
|
|
+ </Space>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="networkType"
|
|
|
|
|
- label={<strong>联网方式</strong>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- getValueFromEvent={(e: string[]) => {
|
|
|
|
|
- if (e.length > 1 && !networkType.includes('0') && e.includes('0')) {
|
|
|
|
|
- return ['0'];
|
|
|
|
|
- }
|
|
|
|
|
- return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' },
|
|
|
|
|
- ...Object.keys(NETWORK_ENUM)?.map(key => ({ label: NETWORK_ENUM[key as keyof typeof NETWORK_ENUM], value: key }))
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className={style.newSpace}>
|
|
|
|
|
+ {isShow('maritalStatus') && <Form.Item name="maritalStatusType" label={<strong>婚恋育儿状态</strong>} style={{ marginBottom: maritalStatusType === '1' ? 4 : 12 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ <Radio value="1">自定义</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>}
|
|
|
|
|
+ {maritalStatusType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
+ <Form.Item name={'maritalStatus'} rules={[{ required: true, message: '请选择婚恋育儿状态' }]}>
|
|
|
|
|
+ <Checkbox.Group options={Object.keys(MARITAL_STATUS_ENUM).map(key => ({ label: MARITAL_STATUS_ENUM[key as keyof typeof MARITAL_STATUS_ENUM], value: key }))} />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ {isShow('excludedConvertedAudience') && <>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name={['excludedConvertedAudience', 'excludedDimension']}
|
|
|
|
|
+ label={<Space>
|
|
|
|
|
+ <strong>排除已转化用户</strong>
|
|
|
|
|
+ <Tooltip title={<>
|
|
|
|
|
+ <Paragraph>
|
|
|
|
|
+ 设置排除已转化定向,广告不会曝光给所选范围内已转化的用户;
|
|
|
|
|
+ 系统将自动以当前广告选择的优化目标作为此定向的转化行为(不支持“点击”、“次留率”优化目标);
|
|
|
|
|
+ 该定向仅可选择oCPC、oCPM、oCPA出价方式,若勾选自定义转化行为则不限制出价方式 。
|
|
|
|
|
+ </Paragraph>
|
|
|
|
|
+ <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3531&pid=2612" target="__blank">了解更多</a>
|
|
|
|
|
+ </>}>
|
|
|
|
|
+ <QuestionCircleFilled />
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ </Space>}
|
|
|
|
|
+ style={{ marginBottom: excludedDimension === '0' ? 12 : 4 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ {Object.keys(EXCLUDED_DIMENSION_ENUM).filter(key => taskType === 'GAME' ? ['EXCLUDED_DIMENSION_UID', 'EXCLUDED_DIMENSION_BUSINESS_MANAGER', 'EXCLUDED_DIMENSION_COMPANY_ACCOUNT', 'EXCLUDED_DIMENSION_APP'].includes(key) : true).map(key => {
|
|
|
|
|
+ return <Radio value={key} key={key}>{EXCLUDED_DIMENSION_ENUM[key as keyof typeof EXCLUDED_DIMENSION_ENUM]}</Radio>
|
|
|
|
|
+ })}
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {excludedDimension !== '0' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
+ <Title level={5} style={{ fontSize: 14 }}>系统自动依照当前广告选择的优化目标作为此定向的转化行为</Title>
|
|
|
|
|
+ <Form.Item label="转化行为" name={'conversionBehaviorType'} rules={[{ required: true, message: '请选择自定义转化行为' }]} style={{ marginBottom: 10 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="optimization">优化目标</Radio>
|
|
|
|
|
+ <Radio value="customize">自定义</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {conversionBehaviorType === 'customize' && <Form.Item name={['excludedConvertedAudience', 'conversionBehaviorList']} rules={[{ required: true, message: '请选择自定义转化行为' }]} style={{ marginBottom: 10 }}>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ showSearch
|
|
|
|
|
+ filterOption={(input, option) =>
|
|
|
|
|
+ (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
|
|
|
|
|
+ }
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ placeholder='请选择自定义转化行为'
|
|
|
|
|
+ mode="multiple"
|
|
|
|
|
+ style={{ width: 480 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {Object.keys(OPTIMIZATIONGOAL_ENUM).filter(key => key !== 'OPTIMIZATIONGOAL_NONE').map(key => {
|
|
|
|
|
+ return <Select.Option value={key} key={key} disabled={conversionBehaviorList?.length >= 2 && !conversionBehaviorList.includes(key)}>{OPTIMIZATIONGOAL_ENUM[key as keyof typeof OPTIMIZATIONGOAL_ENUM]}</Select.Option>
|
|
|
|
|
+ })}
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </Form.Item>}
|
|
|
|
|
+ <Form.Item label="转化时间区间" name={['excludedConvertedAudience', 'excludedDay']}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ {Object.keys(EXCLUDED_DAY_ENUM).map(key => <Radio value={key} key={key}>{EXCLUDED_DAY_ENUM[key as keyof typeof EXCLUDED_DAY_ENUM]}</Radio>)}
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </>}
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- label={<Space>
|
|
|
|
|
- <strong>自定义人群</strong>
|
|
|
|
|
- <Tooltip title={<span>
|
|
|
|
|
- 自定义人群是指客户通过腾讯广告知数(原DMP)创建和管理自己定义类人群,包括您自行上传的号码包人群等。仅当出价方式选择CPC、 CPM或oCPM(且优化目标为“点击”)时,你可以“二方人群”进行投放。
|
|
|
|
|
- <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3161&pid=8995" target="__blank">了解更多</a>
|
|
|
|
|
- </span>}>
|
|
|
|
|
- <QuestionCircleFilled />
|
|
|
|
|
- </Tooltip>
|
|
|
|
|
- </Space>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- >
|
|
|
|
|
- <Space>
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- defaultValue={['0']}
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' }
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- <span style={{ color: '#FAAD14' }}>自定义人群必须关联指定账户,当前关联账户为不限</span>
|
|
|
|
|
- </Space>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {isShow('deviceBrandModel') && <Form.Item name="deviceBrandModelType" label={<strong>设备品牌型号</strong>} style={{ marginBottom: deviceBrandModelType === '0' ? 0 : 4 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ <Radio value="1">自定义</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>}
|
|
|
|
|
+ {deviceBrandModelType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name={'deviceBrandModelList'}
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请选择设备品牌' },
|
|
|
|
|
+ { type: 'array', max: 400, message: '设备品牌最多选择400个设备型号' }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ style={{ marginBottom: 10 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <TreeSelect
|
|
|
|
|
+ placeholder="请选择"
|
|
|
|
|
+ showSearch={true}
|
|
|
|
|
+ maxTagCount={20}
|
|
|
|
|
+ treeCheckable={true}
|
|
|
|
|
+ showCheckedStrategy={TreeSelect.SHOW_CHILD}
|
|
|
|
|
+ treeData={modelList}
|
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
+ if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item name='isExcludedDeviceBrandModel' valuePropName="checked">
|
|
|
|
|
+ <Checkbox>排除所选设备的用户</Checkbox>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="maritalStatusType" label={<strong>婚恋育儿状态</strong>} style={{ marginBottom: maritalStatusType === '1' ? 4 : 12 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- <Radio value="1">自定义</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {maritalStatusType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
- <Form.Item name={'maritalStatus'} rules={[{ required: true, message: '请选择婚恋育儿状态' }]}>
|
|
|
|
|
- <Checkbox.Group options={Object.keys(MARITAL_STATUS_ENUM).map(key => ({ label: MARITAL_STATUS_ENUM[key as keyof typeof MARITAL_STATUS_ENUM], value: key }))} />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name={['excludedConvertedAudience', 'excludedDimension']}
|
|
|
|
|
- label={<Space>
|
|
|
|
|
- <strong>排除已转化用户</strong>
|
|
|
|
|
- <Tooltip title={<>
|
|
|
|
|
- <Paragraph>
|
|
|
|
|
- 设置排除已转化定向,广告不会曝光给所选范围内已转化的用户;
|
|
|
|
|
- 系统将自动以当前广告选择的优化目标作为此定向的转化行为(不支持“点击”、“次留率”优化目标);
|
|
|
|
|
- 该定向仅可选择oCPC、oCPM、oCPA出价方式,若勾选自定义转化行为则不限制出价方式 。
|
|
|
|
|
- </Paragraph>
|
|
|
|
|
- <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3531&pid=2612" target="__blank">了解更多</a>
|
|
|
|
|
- </>}>
|
|
|
|
|
- <QuestionCircleFilled />
|
|
|
|
|
- </Tooltip>
|
|
|
|
|
- </Space>}
|
|
|
|
|
- style={{ marginBottom: excludedDimension === '0' ? 12 : 4 }}
|
|
|
|
|
- >
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- {Object.keys(EXCLUDED_DIMENSION_ENUM).filter(key => taskType === 'GAME' ? ['EXCLUDED_DIMENSION_UID', 'EXCLUDED_DIMENSION_BUSINESS_MANAGER', 'EXCLUDED_DIMENSION_COMPANY_ACCOUNT', 'EXCLUDED_DIMENSION_APP'].includes(key) : true).map(key => {
|
|
|
|
|
- return <Radio value={key} key={key}>{EXCLUDED_DIMENSION_ENUM[key as keyof typeof EXCLUDED_DIMENSION_ENUM]}</Radio>
|
|
|
|
|
- })}
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {excludedDimension !== '0' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
- <Title level={5} style={{ fontSize: 14 }}>系统自动依照当前广告选择的优化目标作为此定向的转化行为</Title>
|
|
|
|
|
- <Form.Item label="转化行为" name={'conversionBehaviorType'} rules={[{ required: true, message: '请选择自定义转化行为' }]} style={{ marginBottom: 10 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="optimization">优化目标</Radio>
|
|
|
|
|
- <Radio value="customize">自定义</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {conversionBehaviorType === 'customize' && <Form.Item name={['excludedConvertedAudience', 'conversionBehaviorList']} rules={[{ required: true, message: '请选择自定义转化行为' }]} style={{ marginBottom: 10 }}>
|
|
|
|
|
- <Select
|
|
|
|
|
- showSearch
|
|
|
|
|
- filterOption={(input, option) =>
|
|
|
|
|
- (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
|
|
|
|
|
- }
|
|
|
|
|
- allowClear
|
|
|
|
|
- placeholder='请选择自定义转化行为'
|
|
|
|
|
- mode="multiple"
|
|
|
|
|
- style={{ width: 480 }}
|
|
|
|
|
|
|
+ {taskType === 'GAME' && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="gameConsumptionLevel"
|
|
|
|
|
+ label={<strong>游戏消费能力</strong>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ getValueFromEvent={(e: string[]) => {
|
|
|
|
|
+ if (e.length > 1 && !gameConsumptionLevel.includes('0') && e.includes('0')) {
|
|
|
|
|
+ return ['0'];
|
|
|
|
|
+ }
|
|
|
|
|
+ return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
+ }}
|
|
|
>
|
|
>
|
|
|
- {Object.keys(OPTIMIZATIONGOAL_ENUM).filter(key => key !== 'OPTIMIZATIONGOAL_NONE').map(key => {
|
|
|
|
|
- return <Select.Option value={key} key={key} disabled={conversionBehaviorList?.length >= 2 && !conversionBehaviorList.includes(key)}>{OPTIMIZATIONGOAL_ENUM[key as keyof typeof OPTIMIZATIONGOAL_ENUM]}</Select.Option>
|
|
|
|
|
- })}
|
|
|
|
|
- </Select>
|
|
|
|
|
- </Form.Item>}
|
|
|
|
|
- <Form.Item label="转化时间区间" name={['excludedConvertedAudience', 'excludedDay']}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- {Object.keys(EXCLUDED_DAY_ENUM).map(key => <Radio value={key} key={key}>{EXCLUDED_DAY_ENUM[key as keyof typeof EXCLUDED_DAY_ENUM]}</Radio>)}
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' },
|
|
|
|
|
+ ...Object.keys(GAME_CONSUMPTION_LEVEL_ENUM)?.map(key => ({ label: GAME_CONSUMPTION_LEVEL_ENUM[key as keyof typeof GAME_CONSUMPTION_LEVEL_ENUM], value: key }))
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- <Form.Item name="deviceBrandModelType" label={<strong>设备品牌型号</strong>} style={{ marginBottom: deviceBrandModelType === '0' ? 0 : 4 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- <Radio value="1">自定义</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {deviceBrandModelType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name={'deviceBrandModelList'}
|
|
|
|
|
- rules={[
|
|
|
|
|
- { required: true, message: '请选择设备品牌' },
|
|
|
|
|
- { type: 'array', max: 400, message: '设备品牌最多选择400个设备型号' }
|
|
|
|
|
- ]}
|
|
|
|
|
- style={{ marginBottom: 10 }}
|
|
|
|
|
- >
|
|
|
|
|
- <TreeSelect
|
|
|
|
|
- placeholder="请选择"
|
|
|
|
|
- showSearch={true}
|
|
|
|
|
- maxTagCount={20}
|
|
|
|
|
- treeCheckable={true}
|
|
|
|
|
- showCheckedStrategy={TreeSelect.SHOW_CHILD}
|
|
|
|
|
- treeData={modelList}
|
|
|
|
|
- style={{ width: '100%' }}
|
|
|
|
|
- allowClear
|
|
|
|
|
- filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
- if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
- return true
|
|
|
|
|
- } else {
|
|
|
|
|
- return false
|
|
|
|
|
|
|
+
|
|
|
|
|
+ {isShow('userOs') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item name="userOsType" label={<strong>操作系统版本</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
+ <Radio.Group>
|
|
|
|
|
+ <Radio value="0">不限</Radio>
|
|
|
|
|
+ <Radio value="1">自定义</Radio>
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {userOsType === '1' && <div className={`${style.newSpace_bottom}`}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name={'os'}
|
|
|
|
|
+ style={{ marginBottom: 10 }}
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请选择操作系统版本' },
|
|
|
|
|
+ { type: 'array', max: 100, message: '操作系统版本最多选择100个设备型号' }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <TreeSelect
|
|
|
|
|
+ placeholder="请选择"
|
|
|
|
|
+ showSearch={true}
|
|
|
|
|
+ maxTagCount={10}
|
|
|
|
|
+ treeCheckable={true}
|
|
|
|
|
+ showCheckedStrategy={TreeSelect.SHOW_PARENT}
|
|
|
|
|
+ treeData={osList}
|
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
+ if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item name='isExcludedOs' valuePropName="checked">
|
|
|
|
|
+ <Checkbox>排除所选操作系统版本的用户</Checkbox>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </div>}
|
|
|
|
|
+
|
|
|
|
|
+ {isShow('devicePrice') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="devicePrice"
|
|
|
|
|
+ label={<strong>设备价格</strong>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ getValueFromEvent={(e: string[]) => {
|
|
|
|
|
+ if (e.length > 1 && !devicePrice.includes('0') && e.includes('0')) {
|
|
|
|
|
+ return ['0'];
|
|
|
}
|
|
}
|
|
|
|
|
+ return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
}}
|
|
}}
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- <Form.Item name='isExcludedDeviceBrandModel' valuePropName="checked">
|
|
|
|
|
- <Checkbox>排除所选设备的用户</Checkbox>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ >
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' },
|
|
|
|
|
+ ...Object.keys(DEVICE_PRICE_ENUM)?.map(key => ({ label: DEVICE_PRICE_ENUM[key as keyof typeof DEVICE_PRICE_ENUM], value: key }))
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>}
|
|
|
|
|
|
|
|
- {taskType === 'GAME' && <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="gameConsumptionLevel"
|
|
|
|
|
- label={<strong>游戏消费能力</strong>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- getValueFromEvent={(e: string[]) => {
|
|
|
|
|
- if (e.length > 1 && !gameConsumptionLevel.includes('0') && e.includes('0')) {
|
|
|
|
|
- return ['0'];
|
|
|
|
|
- }
|
|
|
|
|
- return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
- }}
|
|
|
|
|
|
|
+ {isShow('wechatAdBehavior') && <div className={style.newSpace}>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="wechatAdBehaviorType"
|
|
|
|
|
+ label={<strong>微信再营销</strong>}
|
|
|
|
|
+ style={{ marginBottom: 0 }}
|
|
|
|
|
+ getValueFromEvent={(e: string[]) => {
|
|
|
|
|
+ if (e.length > 1 && !wechatAdBehaviorType.includes('0') && e.includes('0')) {
|
|
|
|
|
+ return ['0'];
|
|
|
|
|
+ }
|
|
|
|
|
+ return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Checkbox.Group
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { label: '不限', value: '0' },
|
|
|
|
|
+ { label: '再营销', value: 'actions' },
|
|
|
|
|
+ { label: '排除营销', value: 'excludedActions' }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {(wechatAdBehaviorType && !wechatAdBehaviorType?.includes('0')) && <div className={`${style.newSpace_bottom}`}>
|
|
|
|
|
+ {wechatAdBehaviorType.includes('actions') && <>
|
|
|
|
|
+ <Title level={5} style={{ fontSize: 14 }}>再营销</Title>
|
|
|
|
|
+ <Form.Item style={{ marginBottom: 10 }} name={['wechatAdBehavior', 'actions']}>
|
|
|
|
|
+ {taskType === 'GAME' ?
|
|
|
|
|
+ <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_GAME_ENUM).filter(item => !['WE_COM_CORP_ID_ADDED', 'WECHAT_WORK_CONTACTS_ADDED'].includes(item)).map(key => ({ label: WECHAT_AD_NEHAVIOR_GAME_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_GAME_ENUM], value: key, disabled: excludedActions?.some((k: string) => k === key) }))} />
|
|
|
|
|
+ :
|
|
|
|
|
+ <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).filter(item => !['WE_COM_CORP_ID_ADDED', 'WECHAT_WORK_CONTACTS_ADDED'].includes(item)).map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_ENUM], value: key, disabled: excludedActions?.some((k: string) => k === key) }))} />
|
|
|
|
|
+ }
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </>}
|
|
|
|
|
+ {wechatAdBehaviorType.includes('excludedActions') && <>
|
|
|
|
|
+ <Title level={5} style={{ fontSize: 14 }}>排除营销</Title>
|
|
|
|
|
+ <Form.Item name={['wechatAdBehavior', 'excludedActions']}>
|
|
|
|
|
+ {taskType === 'GAME' ?
|
|
|
|
|
+ <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_GAME_ENUM).map(key => ({ label: WECHAT_AD_NEHAVIOR_GAME_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_GAME_ENUM], value: key, disabled: actions?.some((k: string) => k === key) }))} />
|
|
|
|
|
+ :
|
|
|
|
|
+ <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_ENUM], value: key, disabled: actions?.some((k: string) => k === key) }))} />
|
|
|
|
|
+ }
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </>}
|
|
|
|
|
+ {excludedActions?.includes('WE_COM_CORP_ID_ADDED') && <>
|
|
|
|
|
+ <Form.Item label={<strong>企业微信</strong>} name={['wechatAdBehavior', 'corpId']} rules={[{ required: true, message: '请选择微信小程序' }]}>
|
|
|
|
|
+ <SelectCorpWechatCorpId />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </>}
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </div>}
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ <Card
|
|
|
|
|
+ title={<strong style={{ fontSize: 18 }}>定向设置</strong>}
|
|
|
|
|
+ className="cardResetCss"
|
|
|
>
|
|
>
|
|
|
- <Checkbox.Group
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' },
|
|
|
|
|
- ...Object.keys(GAME_CONSUMPTION_LEVEL_ENUM)?.map(key => ({ label: GAME_CONSUMPTION_LEVEL_ENUM[key as keyof typeof GAME_CONSUMPTION_LEVEL_ENUM], value: key }))
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item name="userOsType" label={<strong>操作系统版本</strong>} style={{ marginBottom: 0 }}>
|
|
|
|
|
- <Radio.Group>
|
|
|
|
|
- <Radio value="0">不限</Radio>
|
|
|
|
|
- <Radio value="1">自定义</Radio>
|
|
|
|
|
- </Radio.Group>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {userOsType === '1' && <div className={`${style.newSpace_bottom}`}>
|
|
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name={'os'}
|
|
|
|
|
- style={{ marginBottom: 10 }}
|
|
|
|
|
|
|
+ label={<strong>定向模板名称</strong>}
|
|
|
|
|
+ name='targetingName'
|
|
|
|
|
+ // tooltip="下标、日期时分秒、广告账户创建时默认自带"
|
|
|
rules={[
|
|
rules={[
|
|
|
- { required: true, message: '请选择操作系统版本' },
|
|
|
|
|
- { type: 'array', max: 100, message: '操作系统版本最多选择100个设备型号' }
|
|
|
|
|
|
|
+ { required: true, message: '请输入定向模板名称!' },
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true, message: '定向模板名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
|
|
|
|
|
+ let reg = /[&‘’“”/\n\t\f]/ig
|
|
|
|
|
+ if (value && reg.test(value)) {
|
|
|
|
|
+ return Promise.reject()
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.resolve()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true, message: '请确保定向模板名称长度不超过30个字(1个汉字等于2个字符)', validator(_, value) {
|
|
|
|
|
+ if (value && txtLength(value) > 30) {
|
|
|
|
|
+ return Promise.reject()
|
|
|
|
|
+ }
|
|
|
|
|
+ return Promise.resolve()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
]}
|
|
]}
|
|
|
>
|
|
>
|
|
|
- <TreeSelect
|
|
|
|
|
- placeholder="请选择"
|
|
|
|
|
- showSearch={true}
|
|
|
|
|
- maxTagCount={10}
|
|
|
|
|
- treeCheckable={true}
|
|
|
|
|
- showCheckedStrategy={TreeSelect.SHOW_PARENT}
|
|
|
|
|
- treeData={osList}
|
|
|
|
|
- style={{ width: '100%' }}
|
|
|
|
|
- allowClear
|
|
|
|
|
- filterTreeNode={(inputValue: string, treeNode: any) => {
|
|
|
|
|
- if (treeNode.title.includes(inputValue)) {
|
|
|
|
|
- return true
|
|
|
|
|
- } else {
|
|
|
|
|
- return false
|
|
|
|
|
- }
|
|
|
|
|
- }}
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <InputName placeholder='定向模板名称' style={{ width: 480 }} length={30} />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
- <Form.Item name='isExcludedOs' valuePropName="checked">
|
|
|
|
|
- <Checkbox>排除所选操作系统版本的用户</Checkbox>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>}
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="devicePrice"
|
|
|
|
|
- label={<strong>设备价格</strong>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- getValueFromEvent={(e: string[]) => {
|
|
|
|
|
- if (e.length > 1 && !devicePrice.includes('0') && e.includes('0')) {
|
|
|
|
|
- return ['0'];
|
|
|
|
|
- }
|
|
|
|
|
- return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' },
|
|
|
|
|
- ...Object.keys(DEVICE_PRICE_ENUM)?.map(key => ({ label: DEVICE_PRICE_ENUM[key as keyof typeof DEVICE_PRICE_ENUM], value: key }))
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className={style.newSpace}>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="wechatAdBehaviorType"
|
|
|
|
|
- label={<strong>微信再营销</strong>}
|
|
|
|
|
- style={{ marginBottom: 0 }}
|
|
|
|
|
- getValueFromEvent={(e: string[]) => {
|
|
|
|
|
- if (e.length > 1 && !wechatAdBehaviorType.includes('0') && e.includes('0')) {
|
|
|
|
|
- return ['0'];
|
|
|
|
|
- }
|
|
|
|
|
- return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <Checkbox.Group
|
|
|
|
|
- options={[
|
|
|
|
|
- { label: '不限', value: '0' },
|
|
|
|
|
- { label: '再营销', value: 'actions' },
|
|
|
|
|
- { label: '排除营销', value: 'excludedActions' }
|
|
|
|
|
- ]}
|
|
|
|
|
- />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {(wechatAdBehaviorType && !wechatAdBehaviorType?.includes('0')) && <div className={`${style.newSpace_bottom}`}>
|
|
|
|
|
- {wechatAdBehaviorType.includes('actions') && <>
|
|
|
|
|
- <Title level={5} style={{ fontSize: 14 }}>再营销</Title>
|
|
|
|
|
- <Form.Item style={{ marginBottom: 10 }} name={['wechatAdBehavior', 'actions']}>
|
|
|
|
|
- {taskType === 'GAME' ?
|
|
|
|
|
- <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_GAME_ENUM).filter(item => !['WE_COM_CORP_ID_ADDED', 'WECHAT_WORK_CONTACTS_ADDED'].includes(item)).map(key => ({ label: WECHAT_AD_NEHAVIOR_GAME_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_GAME_ENUM], value: key, disabled: excludedActions?.some((k: string) => k === key) }))} />
|
|
|
|
|
- :
|
|
|
|
|
- <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).filter(item => !['WE_COM_CORP_ID_ADDED', 'WECHAT_WORK_CONTACTS_ADDED'].includes(item)).map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_ENUM], value: key, disabled: excludedActions?.some((k: string) => k === key) }))} />
|
|
|
|
|
- }
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </>}
|
|
|
|
|
- {wechatAdBehaviorType.includes('excludedActions') && <>
|
|
|
|
|
- <Title level={5} style={{ fontSize: 14 }}>排除营销</Title>
|
|
|
|
|
- <Form.Item name={['wechatAdBehavior', 'excludedActions']}>
|
|
|
|
|
- {taskType === 'GAME' ?
|
|
|
|
|
- <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_GAME_ENUM).map(key => ({ label: WECHAT_AD_NEHAVIOR_GAME_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_GAME_ENUM], value: key, disabled: actions?.some((k: string) => k === key) }))} />
|
|
|
|
|
- :
|
|
|
|
|
- <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key as keyof typeof WECHAT_AD_NEHAVIOR_ENUM], value: key, disabled: actions?.some((k: string) => k === key) }))} />
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ {!isBackVal && <>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label={<strong>定向模板描述</strong>}
|
|
|
|
|
+ name='description'
|
|
|
|
|
+ >
|
|
|
|
|
+ <Input.TextArea style={{ width: 480 }} placeholder="定向模板描述" />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
- </>}
|
|
|
|
|
- {excludedActions?.includes('WE_COM_CORP_ID_ADDED') && <>
|
|
|
|
|
- <Form.Item label={<strong>企业微信</strong>} name={['wechatAdBehavior', 'corpId']} rules={[{ required: true, message: '请选择微信小程序' }]}>
|
|
|
|
|
- <SelectCorpWechatCorpId />
|
|
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label={<strong>关联账户</strong>}
|
|
|
|
|
+ name='accountId'
|
|
|
|
|
+ >
|
|
|
|
|
+ <Select
|
|
|
|
|
+ style={{ width: 480 }}
|
|
|
|
|
+ showSearch
|
|
|
|
|
+ filterOption={(input, option) =>
|
|
|
|
|
+ (option!.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
|
|
|
|
|
+ }
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ placeholder='请选择媒体账户'
|
|
|
|
|
+ >
|
|
|
|
|
+ {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
|
|
|
|
|
+ </Select>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
</>}
|
|
</>}
|
|
|
- </div>}
|
|
|
|
|
- </div>
|
|
|
|
|
- </Card>
|
|
|
|
|
- <Card
|
|
|
|
|
- title={<strong style={{ fontSize: 18 }}>定向设置</strong>}
|
|
|
|
|
- className="cardResetCss"
|
|
|
|
|
- >
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- label={<strong>定向模板名称</strong>}
|
|
|
|
|
- name='targetingName'
|
|
|
|
|
- // tooltip="下标、日期时分秒、广告账户创建时默认自带"
|
|
|
|
|
- rules={[
|
|
|
|
|
- { required: true, message: '请输入定向模板名称!' },
|
|
|
|
|
- {
|
|
|
|
|
- required: true, message: '定向模板名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
|
|
|
|
|
- let reg = /[&‘’“”/\n\t\f]/ig
|
|
|
|
|
- if (value && reg.test(value)) {
|
|
|
|
|
- return Promise.reject()
|
|
|
|
|
- }
|
|
|
|
|
- return Promise.resolve()
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- required: true, message: '请确保定向模板名称长度不超过30个字(1个汉字等于2个字符)', validator(_, value) {
|
|
|
|
|
- if (value && txtLength(value) > 30) {
|
|
|
|
|
- return Promise.reject()
|
|
|
|
|
- }
|
|
|
|
|
- return Promise.resolve()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- <InputName placeholder='定向模板名称' style={{ width: 480 }} length={30} />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- {!isBackVal && <>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- label={<strong>定向模板描述</strong>}
|
|
|
|
|
- name='description'
|
|
|
|
|
- >
|
|
|
|
|
- <Input.TextArea style={{ width: 480 }} placeholder="定向模板描述" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- label={<strong>关联账户</strong>}
|
|
|
|
|
- name='accountId'
|
|
|
|
|
- >
|
|
|
|
|
- <Select
|
|
|
|
|
- style={{ width: 480 }}
|
|
|
|
|
- showSearch
|
|
|
|
|
- filterOption={(input, option) =>
|
|
|
|
|
- (option!.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
|
|
|
|
|
- }
|
|
|
|
|
- allowClear
|
|
|
|
|
- placeholder='请选择媒体账户'
|
|
|
|
|
- >
|
|
|
|
|
- {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
|
|
|
|
|
- </Select>
|
|
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ <Form.Item className="submit_pull">
|
|
|
|
|
+ <Space>
|
|
|
|
|
+ {isBackVal && !(value && Object.keys(value).length > 0) && <Checkbox checked={isAdd} onChange={(e) => setIsAdd(e.target.checked)}>是否保存到定向模板</Checkbox>}
|
|
|
|
|
+ <Button onClick={onClose}>取消</Button>
|
|
|
|
|
+ <Button type="primary" htmlType="submit" className="modalResetCss" loading={checkTargeting.loading || updateTargeting.loading || addTargeting.loading}>
|
|
|
|
|
+ 确定
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </Space>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
- </>}
|
|
|
|
|
- </Card>
|
|
|
|
|
- <Form.Item className="submit_pull">
|
|
|
|
|
- <Space>
|
|
|
|
|
- {isBackVal && !(value && Object.keys(value).length > 0) && <Checkbox checked={isAdd} onChange={(e) => setIsAdd(e.target.checked)}>是否保存到定向模板</Checkbox>}
|
|
|
|
|
- <Button onClick={onClose}>取消</Button>
|
|
|
|
|
- <Button type="primary" htmlType="submit" className="modalResetCss" loading={checkTargeting.loading || updateTargeting.loading || addTargeting.loading}>
|
|
|
|
|
- 确定
|
|
|
|
|
- </Button>
|
|
|
|
|
- </Space>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </Form>
|
|
|
|
|
|
|
+ </Form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Spin>
|
|
|
</Modal>
|
|
</Modal>
|
|
|
}
|
|
}
|
|
|
|
|
|