素材云添加前测参数
This commit is contained in:
+77
-77
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-BZEL1bMy.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-1NS8asx8.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D3IaMBsq.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -503,7 +503,6 @@ export async function getDefaultPreTest(): Promise<any> {
|
||||
export async function updateHotOpeningVideoPromptSchema(projectId: string, stepId: string, params: { prompt_schema: Record<string, any> }): Promise<any> {
|
||||
return api.put(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/video-prompt-schema`, params);
|
||||
}
|
||||
|
||||
// 镜头复刻
|
||||
export async function createShotReplication(params: any): Promise<any> {
|
||||
return api.post('/shot-replications/task-sets', params);
|
||||
@@ -519,68 +518,50 @@ export async function getShotReplicationList(page: number, page_size: number, ke
|
||||
export async function getShotReplicationDetail(taskSetId: string): Promise<any> {
|
||||
return api.get(`/shot-replications/task-sets/${taskSetId}`);
|
||||
}
|
||||
|
||||
// ai拆镜
|
||||
export async function createRemoveLens(taskSetId: string, params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/task-sets/${taskSetId}/split-by-ai`, params);
|
||||
}
|
||||
|
||||
// 手动分割
|
||||
export async function splitCustom(taskSetId: string, params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/task-sets/${taskSetId}/split-custom`, params);
|
||||
}
|
||||
|
||||
// 拆镜列表
|
||||
export async function Removelist(taskSetId: string): Promise<any> {
|
||||
return api.get(`/shot-replications/task-sets/${taskSetId}/segments`);
|
||||
}
|
||||
|
||||
// 生成视频
|
||||
export async function removeCreate(recordId: string, params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/segments/${recordId}/replication-projects`, params);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 获取爆款开头复刻任务详情
|
||||
export async function removeDetail(id: string): Promise<any> {
|
||||
return api.get(`/shot-replications/projects/${id}`);
|
||||
}
|
||||
|
||||
// 第一步,生成提示词
|
||||
export async function removeone(projectId: string, stepId: string): Promise<any> {
|
||||
return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-image-prompt`);
|
||||
|
||||
}
|
||||
|
||||
// 修改图片提示词
|
||||
export async function updateShotImagePrompt(projectId: string, stepId: string, params: any): Promise<any> {
|
||||
return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/image-prompt`, params);
|
||||
}
|
||||
|
||||
// 修改视频提示词
|
||||
export async function updateShotVideoPromptSchema(projectId: string, stepId: string, params: any): Promise<any> {
|
||||
return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/video-prompt-schema`, params);
|
||||
}
|
||||
|
||||
// 第二步,生成图片
|
||||
export async function removetwo(projectId: string, stepId: string ,params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-image`, params);
|
||||
}
|
||||
|
||||
// 第三步,生成视频提示词
|
||||
export async function removethree(projectId: string, stepId: string ,params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-video-prompt`, params);
|
||||
|
||||
|
||||
}
|
||||
// 第四步,生成视频
|
||||
export async function removefour(projectId: string, stepId: string ,params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-video`, params);
|
||||
|
||||
}
|
||||
|
||||
// 获取地区信息
|
||||
export interface GetAreaParams {
|
||||
level?: string;
|
||||
@@ -592,23 +573,21 @@ export async function getArea(params?: GetAreaParams): Promise<any> {
|
||||
if (params?.parent_code) query.set('parent_code', params.parent_code);
|
||||
return api.get(`/pre-test-template/getArea?${query.toString()}`);
|
||||
}
|
||||
|
||||
// ── Upload Material ───────────────────────────────────────
|
||||
|
||||
export interface AsyncBatchUploadTask {
|
||||
advertiser_ids: string[];
|
||||
resource_ids: string[];
|
||||
oauth_id: string;
|
||||
is_pre_test: string;
|
||||
pre_test_template: string;
|
||||
source_model: string;
|
||||
}
|
||||
|
||||
export interface AsyncBatchUploadParams {
|
||||
tasks: AsyncBatchUploadTask[];
|
||||
}
|
||||
|
||||
export async function asyncBatchUploadMaterial(params: AsyncBatchUploadParams): Promise<any> {
|
||||
return api.post('/upload-material/async-batch-upload', params);
|
||||
}
|
||||
|
||||
// 获取上传历史
|
||||
// /api/upload-material/upload-history
|
||||
export interface UploadHistoryParams {
|
||||
@@ -624,7 +603,6 @@ export async function getUploadHistory(params: UploadHistoryParams): Promise<any
|
||||
const query = searchParams.toString();
|
||||
return api.get(`/upload-material/upload-history${query ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
export interface UploadFilenames {
|
||||
source_id: string;
|
||||
file_name: string;
|
||||
@@ -636,7 +614,6 @@ export interface UpdateFilenameParams {
|
||||
export async function updateFilename(params: UpdateFilenameParams): Promise<any> {
|
||||
return api.post('/upload-material/batch-update-filename', params);
|
||||
}
|
||||
|
||||
// 查询素材消耗列表
|
||||
export interface MaterialConsumpListParams {
|
||||
advertiser_id?: string;
|
||||
@@ -652,7 +629,6 @@ export async function getMaterialConsumpList(params: MaterialConsumpListParams):
|
||||
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
||||
return api.get(`/material-consumption/list?${query.toString()}`);
|
||||
}
|
||||
|
||||
// 拉取消耗
|
||||
export async function syncMaterialConsumption(params: { date: string; advertiser_id?: string }): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
@@ -660,12 +636,10 @@ export async function syncMaterialConsumption(params: { date: string; advertiser
|
||||
if (params.date) query.set('date', params.date);
|
||||
return api.get(`/material-consumption/sync?${query.toString()}`);
|
||||
}
|
||||
|
||||
// 获取消耗字段列表
|
||||
export async function getMaterialConsumptionFields(): Promise<any> {
|
||||
return api.get('/material-consumption/fields');
|
||||
}
|
||||
|
||||
// ── Contact ────────────────────────────────────────────────
|
||||
export interface ContactRequestParams {
|
||||
phone: string;
|
||||
@@ -674,11 +648,9 @@ export interface ContactRequestParams {
|
||||
name: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export async function createContactRequest(params: ContactRequestParams): Promise<any> {
|
||||
return api.post('/contact/request', params);
|
||||
}
|
||||
|
||||
// 查询上传素材列表
|
||||
export interface ResourcesMaterialListParams {
|
||||
advertiser_id?: string;
|
||||
|
||||
@@ -2,15 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Button, Table, Tag, Modal, Select, App, Input, Pagination, Typography, Space } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PlusOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { getOAuthList, requestOAuth, getOpenTypeAll } from '../api';
|
||||
|
||||
const PORT_TYPE_MAP: Record<number, string> = {
|
||||
1: '巨量',
|
||||
2: '磁力',
|
||||
3: '巨量星图',
|
||||
4: '服务单',
|
||||
5: '腾讯',
|
||||
};
|
||||
import { getOAuthList, requestOAuth, getOpenTypeAll, getDefaultPreTest } from '../api';
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
@@ -23,7 +15,6 @@ const formatDateTime = (dateStr: string) => {
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
// 安全拼接URL,避免双斜杠
|
||||
const buildUrl = (path: string): string => {
|
||||
const baseUrl = import.meta.env.VITE_API_BASE || "http://localhost:8000";
|
||||
@@ -37,7 +28,6 @@ const buildUrl = (path: string): string => {
|
||||
const cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
||||
return `${cleanBase}/${cleanPath}`;
|
||||
};
|
||||
|
||||
interface AuthorizationData {
|
||||
id: string;
|
||||
status: string;
|
||||
@@ -79,8 +69,17 @@ const AuthorizationPage: React.FC = () => {
|
||||
useEffect(() => {
|
||||
loadOAuthList();
|
||||
loadOpenTypeList();
|
||||
list();
|
||||
}, []);
|
||||
|
||||
|
||||
const list = async () => {
|
||||
try {
|
||||
const res = await getDefaultPreTest();
|
||||
console.log(res);
|
||||
} catch (error) {
|
||||
}
|
||||
};
|
||||
const loadOpenTypeList = async () => {
|
||||
try {
|
||||
const res = await getOpenTypeAll();
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
UploadOutlined,
|
||||
|
||||
} from '@ant-design/icons';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll } from '../api';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll, getPreTestList } from '../api';
|
||||
|
||||
const { Search } = Input;
|
||||
const { Text } = Typography;
|
||||
@@ -68,6 +68,11 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [selectedHistoryAccounts, setSelectedHistoryAccounts] = useState<any[]>([]);
|
||||
const [openTypeMap, setOpenTypeMap] = useState<Record<number, string>>({});
|
||||
|
||||
const [isPreTest, setIsPreTest] = useState<string>('2');
|
||||
const [preTestTemplate, setPreTestTemplate] = useState<string>('');
|
||||
const [preTestTemplates, setPreTestTemplates] = useState<any[]>([]);
|
||||
const [preTestTemplatesLoading, setPreTestTemplatesLoading] = useState(false);
|
||||
|
||||
// 推送任务历史弹窗相关状态
|
||||
const [uploadHistoryModalVisible, setUploadHistoryModalVisible] = useState(false);
|
||||
const [uploadHistoryList, setUploadHistoryList] = useState<any[]>([]);
|
||||
@@ -669,6 +674,18 @@ const GeneratedRecord: React.FC = () => {
|
||||
message.destroy('downloadProgress');
|
||||
message.success(`下载完成,共 ${successCount} 个文件`);
|
||||
};
|
||||
const loadPreTestTemplates = async () => {
|
||||
setPreTestTemplatesLoading(true);
|
||||
try {
|
||||
const res = await getPreTestList({ page: 1, pageSize: 100 });
|
||||
setPreTestTemplates(res.data?.data || res.data || []);
|
||||
} catch (error) {
|
||||
console.error('加载前测模板失败:', error);
|
||||
} finally {
|
||||
setPreTestTemplatesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchUploadSelected = () => {
|
||||
if (selectedItems.size === 0) {
|
||||
message.warning('请先选择要推送的媒体');
|
||||
@@ -677,6 +694,9 @@ const GeneratedRecord: React.FC = () => {
|
||||
setAccountIdLists([[]]);
|
||||
setAccountIdInputs(['']);
|
||||
setSelectedOauthItems([undefined]);
|
||||
setIsPreTest('2');
|
||||
setPreTestTemplate('');
|
||||
loadPreTestTemplates();
|
||||
setUploadConfigModalVisible(true);
|
||||
};
|
||||
const handleSinglePushToMedia = () => {
|
||||
@@ -688,6 +708,9 @@ const GeneratedRecord: React.FC = () => {
|
||||
setSelectedOauthItems([undefined]);
|
||||
setMaterialFileNames(new Map());
|
||||
setUnifiedFileName('');
|
||||
setIsPreTest('2');
|
||||
setPreTestTemplate('');
|
||||
loadPreTestTemplates();
|
||||
setUploadConfigModalVisible(true);
|
||||
};
|
||||
const loadOAuthList = async (page: number, pageSize: number) => {
|
||||
@@ -779,6 +802,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
let tasks: {
|
||||
advertiser_ids: string[];
|
||||
resource_ids: string[];
|
||||
is_pre_test: string;
|
||||
pre_test_template: string;
|
||||
oauth_id: string;
|
||||
source_model: string;
|
||||
}[] = [];
|
||||
@@ -815,6 +840,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
resource_ids: resourceIds,
|
||||
oauth_id: oauthItem.value,
|
||||
source_model: sourceModel,
|
||||
is_pre_test: isPreTest,
|
||||
pre_test_template: preTestTemplate,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -844,6 +871,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
resource_ids: resourceIds,
|
||||
oauth_id: String(account.oauthId),
|
||||
source_model: sourceModel,
|
||||
is_pre_test: isPreTest,
|
||||
pre_test_template: preTestTemplate,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -868,6 +897,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
setSelectedHistoryAccounts([]);
|
||||
setMaterialFileNames(new Map());
|
||||
setUnifiedFileName('');
|
||||
setIsPreTest('2');
|
||||
setPreTestTemplate('');
|
||||
} catch (error: any) {
|
||||
console.error('批量推送失败:', error);
|
||||
message.error(error.message || '批量推送失败');
|
||||
@@ -1202,7 +1233,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 14 }}>媒体类型:</Typography.Text>
|
||||
|
||||
<Space>
|
||||
<Button
|
||||
type={filterMedia === 'video' ? 'primary' : 'default'}
|
||||
@@ -1512,7 +1542,41 @@ const GeneratedRecord: React.FC = () => {
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Typography.Text style={{ fontSize: 14, color: '#475569' }}>是否开启前测:</Typography.Text>
|
||||
<Select
|
||||
value={isPreTest}
|
||||
onChange={(value) => {
|
||||
setIsPreTest(value);
|
||||
if (value !== '1') {
|
||||
setPreTestTemplate('');
|
||||
}
|
||||
}}
|
||||
style={{ width: 120 }}
|
||||
options={[
|
||||
{ value: '1', label: '是' },
|
||||
{ value: '2', label: '否' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Typography.Text style={{ fontSize: 14, color: '#475569' }}>前测模板:</Typography.Text>
|
||||
<Select
|
||||
value={preTestTemplate}
|
||||
onChange={(value) => setPreTestTemplate(value)}
|
||||
style={{ width: 200 }}
|
||||
disabled={isPreTest !== '1'}
|
||||
loading={preTestTemplatesLoading}
|
||||
placeholder={isPreTest !== '1' ? '请先开启前测' : '选择前测模板'}
|
||||
options={preTestTemplates.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs
|
||||
size="small"
|
||||
activeKey={accountTab}
|
||||
@@ -1533,7 +1597,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{accountTab === 'new' && (
|
||||
<div>
|
||||
{selectedOauthItems.map((oauthItem, index) => (
|
||||
@@ -1728,7 +1791,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{accountTab === 'history' && (
|
||||
<div style={{ padding: 8 }}>
|
||||
{historyOAuthLoading ? (
|
||||
|
||||
+221
-195
@@ -19,13 +19,6 @@ interface PreTestField {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const statusConfig = {
|
||||
completed: { label: '已完成', color: 'success' },
|
||||
pending: { label: '待测试', color: 'default' },
|
||||
testing: { label: '测试中', color: 'processing' },
|
||||
failed: { label: '测试失败', color: 'error' },
|
||||
};
|
||||
|
||||
const PRETEST_FIELDS: PreTestField[] = [
|
||||
{ id: 'AD_APP_ACTIVATE', name: 'AD_APP_ACTIVATE', label: '应用-激活', description: '应用激活行为' },
|
||||
{ id: 'AD_APP_AUTH', name: 'AD_APP_AUTH', label: '应用-授信', description: '应用授信行为' },
|
||||
@@ -129,12 +122,24 @@ const PRETEST_FIELDS: PreTestField[] = [
|
||||
{ id: 'QC_PRODUCT_ROI', name: 'QC_PRODUCT_ROI', label: '商品投放-商品支付roi', description: '商品支付roi' },
|
||||
];
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '前测模板ID',
|
||||
title: '前测模板编号',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 110,
|
||||
width: 120,
|
||||
render: (text: string) => <Typography.Text strong style={{ color: '#1e293b', fontSize: 13 }}>{text}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
@@ -273,13 +278,13 @@ const columns = [
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 160,
|
||||
render: (text: string) => <Typography.Text style={{ color: '#94a3b8', fontSize: 12 }}>{text}</Typography.Text>,
|
||||
render: (text: string) => <Typography.Text style={{ color: '#94a3b8', fontSize: 12 }}>{formatDateTime(text)}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right' as const,
|
||||
key: 'operation',
|
||||
width: 140,
|
||||
render: (_: any, record: PreTestRecord) => (
|
||||
<Space size={4}>
|
||||
<Button
|
||||
@@ -320,7 +325,6 @@ const PreTest: React.FC = () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getPreTestList({ page: currentPage, pageSize });
|
||||
console.log(res);
|
||||
setRecords(res.data || []);
|
||||
setTotal(res.pagination?.total || 0);
|
||||
setCurrentPage(res.pagination?.page || 1);
|
||||
@@ -514,122 +518,154 @@ const PreTest: React.FC = () => {
|
||||
layout="vertical"
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="模板名称"
|
||||
rules={[{ required: true, message: '请输入模板名称' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入模板名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="platform"
|
||||
label="平台"
|
||||
initialValue="AD"
|
||||
rules={[{ required: true, message: '请选择平台' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择平台"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
onChange={(value) => {
|
||||
setPlatform(value);
|
||||
}}
|
||||
options={[
|
||||
{ value: 'AD', label: 'AD' },
|
||||
{ value: 'QIANCHUAN', label: '千川' },
|
||||
{ value: 'LOCAL', label: '本地推' },
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="name"
|
||||
label="模板名称"
|
||||
rules={[{ required: true, message: '请输入模板名称' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入模板名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="platform"
|
||||
label="平台"
|
||||
initialValue="AD"
|
||||
rules={[{ required: true, message: '请选择平台' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择平台"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
onChange={(value) => {
|
||||
setPlatform(value);
|
||||
}}
|
||||
options={[
|
||||
{ value: 'AD', label: 'AD' },
|
||||
{ value: 'QIANCHUAN', label: '千川' },
|
||||
{ value: 'LOCAL', label: '本地推' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{platform !== 'LOCAL' && (
|
||||
<Form.Item
|
||||
name="external_action"
|
||||
label="转化目标"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择转化目标(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={PRETEST_FIELDS.filter(field => {
|
||||
if (platform === 'AD') {
|
||||
return field.name.startsWith('AD_');
|
||||
} else if (platform === 'QIANCHUAN') {
|
||||
return field.name.startsWith('QC_');
|
||||
} else if (platform === 'LOCAL') {
|
||||
return field.name.startsWith('LOCAL_');
|
||||
}
|
||||
return true;
|
||||
}).map(field => ({
|
||||
value: field.name,
|
||||
label: (
|
||||
<span>
|
||||
{field.label}
|
||||
{field.description && (
|
||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 12, marginLeft: 4 }}>
|
||||
({field.description})
|
||||
</Typography.Text>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpa_bid"
|
||||
label="目标转化成本"
|
||||
rules={[
|
||||
{ type: 'number', min: 1, max: 10000, message: '取值范围: 1-10000' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="取值范围: 1-10000,精确到小数点后2位"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={0.01}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="external_action"
|
||||
label="转化目标"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择转化目标(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={PRETEST_FIELDS.filter(field => {
|
||||
if (platform === 'AD') {
|
||||
return field.name.startsWith('AD_');
|
||||
} else if (platform === 'QIANCHUAN') {
|
||||
return field.name.startsWith('QC_');
|
||||
} else if (platform === 'LOCAL') {
|
||||
return field.name.startsWith('LOCAL_');
|
||||
}
|
||||
return true;
|
||||
}).map(field => ({
|
||||
value: field.name,
|
||||
label: (
|
||||
<span>
|
||||
{field.label}
|
||||
{field.description && (
|
||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 12, marginLeft: 4 }}>
|
||||
({field.description})
|
||||
</Typography.Text>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="cpa_bid"
|
||||
label="目标转化成本"
|
||||
rules={[
|
||||
{ type: 'number', min: 1, max: 10000, message: '取值范围: 1-10000' },
|
||||
]}
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="取值范围: 1-10000,精确到小数点后2位"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={0.01}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="audience_gender"
|
||||
label="受众性别"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择受众性别"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: 'MALE', label: '男' },
|
||||
{ value: 'FEMALE', label: '女' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="audience_age"
|
||||
label="受众年龄"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择受众年龄(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '18-23', label: '18-23岁' },
|
||||
{ value: '24-30', label: '24-30岁' },
|
||||
{ value: '31-40', label: '31-40岁' },
|
||||
{ value: '41-49', label: '41-49岁' },
|
||||
{ value: '50', label: '50岁以上' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_gender"
|
||||
label="受众性别"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择受众性别"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: 'MALE', label: '男' },
|
||||
{ value: 'FEMALE', label: '女' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_age"
|
||||
label="受众年龄"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择受众年龄(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '18-23', label: '18-23岁' },
|
||||
{ value: '24-30', label: '24-30岁' },
|
||||
{ value: '31-40', label: '31-40岁' },
|
||||
{ value: '41-49', label: '41-49岁' },
|
||||
{ value: '50', label: '50岁以上' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_network"
|
||||
label="网络类型"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择网络类型(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '5G', label: '5G' },
|
||||
{ value: '4G', label: '4G' },
|
||||
{ value: '3G', label: '3G' },
|
||||
{ value: '2G', label: '2G' },
|
||||
{ value: 'WIFI', label: 'wifi' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="audience_region"
|
||||
label="受众地区"
|
||||
@@ -644,53 +680,37 @@ const PreTest: React.FC = () => {
|
||||
maxTagCount="responsive"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="audience_network"
|
||||
label="网络类型"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择网络类型(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '5G', label: '5G' },
|
||||
{ value: '4G', label: '4G' },
|
||||
{ value: '3G', label: '3G' },
|
||||
{ value: '2G', label: '2G' },
|
||||
{ value: 'WIFI', label: 'wifi' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="cus_name"
|
||||
label="客户主体名称"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入客户主体名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="pricing_type"
|
||||
label="出价类型"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择出价类型"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'OCPC', label: 'OCPC' },
|
||||
{ value: 'CPA', label: 'CPA' },
|
||||
{ value: 'OCPM', label: 'OCPM' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cus_name"
|
||||
label="客户主体名称"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入客户主体名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="pricing_type"
|
||||
label="出价类型"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择出价类型"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'OCPC', label: 'OCPC' },
|
||||
{ value: 'CPA', label: 'CPA' },
|
||||
{ value: 'OCPM', label: 'OCPM' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{platform == 'AD' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cost_cap"
|
||||
label="是否最优成本出价"
|
||||
valuePropName="checked"
|
||||
@@ -706,6 +726,7 @@ const PreTest: React.FC = () => {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="target_cost"
|
||||
label="是否稳定成本出价"
|
||||
valuePropName="checked"
|
||||
@@ -721,6 +742,7 @@ const PreTest: React.FC = () => {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="nobid"
|
||||
label="是否最大转化出价"
|
||||
valuePropName="checked"
|
||||
@@ -734,38 +756,42 @@ const PreTest: React.FC = () => {
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
<Form.Item
|
||||
name="cpc_bid"
|
||||
label="目标点击成本"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入目标点击成本"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpc_bid"
|
||||
label="目标点击成本"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入目标点击成本"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="budget"
|
||||
label="预算金额"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入预算金额"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="budget"
|
||||
label="预算金额"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入预算金额"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="note"
|
||||
label="备注"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入备注"
|
||||
rows={3}
|
||||
rows={2}
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user