Commit 72d71524 authored by huangzhenglong's avatar huangzhenglong

feat: 模版页面和任务页面新增tiktok特殊字段

parent ec521995
......@@ -163,6 +163,99 @@
<el-input v-model="form.tempName" :placeholder="selectedTemplate.name" disabled></el-input>
</el-form-item>
<el-form-item label="平台" prop="platform">
<el-select v-model="form.platform" placeholder="请选择平台" @change="handlePlatformChange">
<el-option label="Google" :value="1"></el-option>
<el-option label="TikTok" :value="2"></el-option>
<el-option label="Facebook" :value="3"></el-option>
</el-select>
</el-form-item>
<!-- TikTok特定字段 -->
<template v-if="form.platform === 2">
<el-form-item label="智能创建" prop="tiktok_json.isSmart">
<el-switch
v-model="form.tiktok_json.isSmart"
:active-text="'开启'"
:inactive-text="'关闭'">
</el-switch>
</el-form-item>
<el-form-item label="优化目标" prop="tiktok_json.optimizationGoal">
<el-select v-model="form.tiktok_json.optimizationGoal" placeholder="请选择优化目标">
<el-option label="价值优化" value="VALUE"></el-option>
<el-option label="应用安装" value="INSTALL"></el-option>
</el-select>
</el-form-item>
<el-form-item label="深度出价类型" prop="tiktok_json.deepBidType">
<el-select v-model="form.tiktok_json.deepBidType" placeholder="请选择深度出价类型">
<el-option label="最小ROAS" value="VO_MIN_ROAS"></el-option>
<el-option label="最高价值" value="VO_HIGHEST_VALUE"></el-option>
</el-select>
</el-form-item>
<el-form-item label="深度CPA出价" prop="tiktok_json.deepCpaBid">
<el-input-number v-model="form.tiktok_json.deepCpaBid" :min="0" :precision="2"></el-input-number>
</el-form-item>
<el-form-item label="投放位置" prop="tiktok_json.placements">
<el-select v-model="form.tiktok_json.placements" multiple placeholder="请选择投放位置">
<el-option label="TikTok" value="PLACEMENT_TIKTOK"></el-option>
<el-option label="Pangle" value="PLACEMENT_PANGLE"></el-option>
<el-option label="Global App Bundle" value="PLACEMENT_GLOBAL_APP_BUNDLE"></el-option>
</el-select>
</el-form-item>
<el-form-item label="是否新建计划" prop="isNewCampaign">
<el-switch
v-model="form.isNewCampaign"
active-text="是"
inactive-text="否">
</el-switch>
</el-form-item>
<template v-if="!form.isNewCampaign">
<el-form-item
label="选择账户"
prop="advertiserId">
<el-select
v-model="form.advertiserId"
placeholder="请选择账户"
:loading="advertiserLoading"
@change="handleAdvertiserChange">
<el-option
v-for="item in advertiserOptions"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="选择已有计划"
prop="campaignIdList"
v-if="form.advertiserId">
<el-select
v-model="form.campaignIdList"
multiple
filterable
placeholder="请输入计划ID搜索"
:loading="campaignListLoading"
style="width: 100%">
<el-option
v-for="item in campaignOptions"
:key="item"
:label="item"
:value="item">
<span style="float: left">{{ item }}</span>
</el-option>
</el-select>
</el-form-item>
</template>
</template>
<el-form-item label="广告系列类型" prop="campaign_type">
<el-select v-model="form.campaign_type" placeholder="请选择">
<el-option label="应用安装" :value="1"></el-option>
......@@ -592,7 +685,19 @@ export default {
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
description_groups: [],
// 新增TikTok特定字段
isNewCampaign: true,
advertiserId: '', // 新增:选中的广告主ID
campaignIdList: [],
// 修改字段名为tiktok_json
tiktok_json: {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 添加isSmart字段,默认为false
}
},
selectedTemplate: null,
rules: {
......@@ -625,6 +730,20 @@ export default {
],
location_groups: [
{ required: true, type: 'array', message: '请选择地域组', trigger: 'change' }
],
// 修改TikTok特定字段的验证规则
'tiktok_json.optimizationGoal': [
{ required: true, message: '请选择优化目标', trigger: 'change' }
],
'tiktok_json.deepBidType': [
{ required: true, message: '请选择深度出价类型', trigger: 'change' }
],
'tiktok_json.deepCpaBid': [
{ required: true, message: '请输入深度CPA出价', trigger: 'blur' },
{ type: 'number', min: 0, message: '深度CPA出价必须大于0', trigger: 'blur' }
],
'tiktok_json.placements': [
{ required: true, message: '请选择投放位置', trigger: 'change' }
]
},
templateMap: new Map(),
......@@ -653,6 +772,14 @@ export default {
selectedAppId: '',
selectApps: [],
appsLoading: false,
campaignListLoading: false,
campaignOptions: [],
// 新增:账户相关数据
advertiserOptions: [],
advertiserLoading: false,
// 新增:搜索相关数据
campaignSearchKeyword: '',
filteredCampaignOptions: [],
}
},
created() {
......@@ -779,7 +906,19 @@ export default {
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
description_groups: [],
// 新增TikTok特定字段
isNewCampaign: true,
advertiserId: '', // 新增:选中的广告主ID
campaignIdList: [],
// 修改字段名为tiktok_json
tiktok_json: {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 添加isSmart字段,默认为false
}
}
this.selectedTemplate = null
......@@ -918,6 +1057,17 @@ export default {
taskData.tempTemplateName = this.form.tempTemplateName;
}
// 如果是TikTok平台,添加特定字段
if (this.form.platform === 2) {
taskData.isNewCampaign = this.form.isNewCampaign;
if (!this.form.isNewCampaign) {
taskData.advertiserId = this.form.advertiserId;
taskData.campaignIdList = this.form.campaignIdList;
}
// 修改字段名
taskData.tiktok_json = JSON.stringify(this.form.tiktok_json);
}
// 将应用列表数据转换为接口所需格式
taskData.apps = this.appListData.map(app => ({
pkg: app.appId,
......@@ -1042,32 +1192,80 @@ export default {
// 选择模板后的处理
async handleTemplateSelectorChange(templateId) {
if (!templateId) {
this.selectedTemplate = null
return
this.selectedTemplate = null;
return;
}
// 确保模板ID被设置到form中
this.form.campaignTemplateId = templateId
this.form.campaignTemplateId = templateId;
try {
const response = await getCampaignTemplateById(templateId)
console.log('模板详情响应:', response)
const response = await getCampaignTemplateById(templateId);
console.log('模板详情响应:', response);
if (response.result && response.status === 200) {
const template = response.result.data
this.selectedTemplate = template
// 将模板数据填充到表单中以便编辑
this.form.tempName = template.name
this.form.campaign_type = template.campaign_type || 1
this.form.appStore = template.appStore || 2
this.form.daily_budget = template.daily_budget || 100
this.form.bidding_type = template.bidding_type || 5
this.form.target_roas = template.target_roas || 100
this.form.app_groups = [...(template.app_groups || [])]
this.form.location_groups = [...(template.location_groups || [])]
this.form.material_groups = [...(template.material_groups || [])]
this.form.title_groups = [...(template.title_groups || [])]
this.form.description_groups = [...(template.description_groups || [])]
const template = response.result.data;
this.selectedTemplate = template;
// 将模板数据填充到表单中
this.form.tempName = template.name;
this.form.platform = template.platform || 1;
this.form.campaign_type = template.campaign_type || 1;
this.form.appStore = template.appStore || 2;
this.form.daily_budget = template.daily_budget || 100;
this.form.bidding_type = template.bidding_type || 5;
this.form.target_roas = template.target_roas || 100;
this.form.app_groups = [...(template.app_groups || [])];
this.form.location_groups = [...(template.location_groups || [])];
this.form.material_groups = [...(template.material_groups || [])];
this.form.title_groups = [...(template.title_groups || [])];
this.form.description_groups = [...(template.description_groups || [])];
// 处理TikTok特定字段
if (template.platform === 2) {
try {
let tiktokData;
if (template.tiktok_json) {
tiktokData = typeof template.tiktok_json === 'string' ?
JSON.parse(template.tiktok_json) : template.tiktok_json;
} else {
// 如果没有tiktok_json,使用默认值
tiktokData = {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 添加isSmart字段,默认为false
};
}
// 确保所有必要的字段都存在
this.form.tiktok_json = {
optimizationGoal: tiktokData.optimizationGoal || 'VALUE',
deepBidType: tiktokData.deepBidType || 'VO_MIN_ROAS',
deepCpaBid: tiktokData.deepCpaBid || 0,
placements: Array.isArray(tiktokData.placements) ? tiktokData.placements : [],
isSmart: tiktokData.isSmart || false // 添加isSmart字段处理
};
console.log('设置TikTok配置:', this.form.tiktok_json);
} catch (error) {
console.error('解析TikTok配置失败:', error);
this.$message.warning('TikTok配置解析失败,使用默认值');
// 设置默认值
this.form.tiktok_json = {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 添加默认值
};
}
}
// 如果是TikTok平台,获取广告主列表
if (template.platform === 2) {
await this.fetchAdvertiserList();
}
// 主动触发应用列表和地域组列表的查询
this.$nextTick(() => {
......@@ -1081,8 +1279,85 @@ export default {
});
}
} catch (error) {
console.error('获取模板详情失败:', error)
this.$message.error('获取模板详情失败')
console.error('获取模板详情失败:', error);
this.$message.error('获取模板详情失败');
}
},
// 获取广告主列表
async fetchAdvertiserList() {
this.advertiserLoading = true;
try {
const response = await axios.get(process.env.PUTIN_API + '/campaign-tasks/getTiktokAdvertiserId');
if (response.data && response.data.status === 200) {
this.advertiserOptions = response.data.result.data || [];
} else {
this.$message.error(response.data.msg || '获取账户列表失败');
}
} catch (error) {
console.error('获取账户列表失败:', error);
this.$message.error('获取账户列表失败');
} finally {
this.advertiserLoading = false;
}
},
// 处理广告主变化
async handleAdvertiserChange(advertiserId) {
if (!advertiserId) {
this.campaignOptions = [];
this.form.campaignIdList = [];
return;
}
await this.fetchCampaignList(advertiserId);
},
// 修改搜索处理方法
handleCampaignSearch(query) {
if (!query) {
this.filteredCampaignOptions = this.campaignOptions;
} else {
this.filteredCampaignOptions = this.campaignOptions.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
}
},
// 修改获取计划列表的方法
async fetchCampaignList(advertiserId) {
if (!advertiserId) return;
this.campaignListLoading = true;
try {
const response = await axios.get(process.env.PUTIN_API + '/campaign-tasks/getCampaignIdByAdvertiserId', {
params: {
advertiserId: advertiserId
}
});
if (response.data && response.data.status === 200) {
this.campaignOptions = response.data.result.data || [];
this.form.campaignIdList = [];
} else {
this.$message.error(response.data.msg || '获取计划列表失败');
}
} catch (error) {
console.error('获取计划列表失败:', error);
this.$message.error('获取计划列表失败');
} finally {
this.campaignListLoading = false;
}
},
// 处理平台变化
async handlePlatformChange(value) {
if (value === 2) {
// 如果选择了TikTok平台,重置相关字段
this.form.isNewCampaign = true;
this.form.advertiserId = '';
this.form.campaignIdList = [];
// 获取广告主列表
await this.fetchAdvertiserList();
}
},
......@@ -1782,4 +2057,14 @@ export default {
.app-selector-container .el-select {
width: 100%;
}
.campaign-select-container {
.campaign-search-input {
margin-bottom: 10px;
}
.campaign-select {
width: 100%;
}
}
</style>
......@@ -212,6 +212,55 @@
<el-input-number v-model="form.target_roas" :min="0"></el-input-number>
</el-form-item>
<!-- TikTok特定字段 -->
<template v-if="form.platform === 2">
<el-form-item label="智能创建" prop="tiktok_json.isSmart">
<el-switch
v-model="tiktokIsSmart"
:active-text="'开启'"
:inactive-text="'关闭'">
</el-switch>
</el-form-item>
<el-form-item label="优化目标" prop="tiktok_json.optimizationGoal">
<el-select
v-model="tiktokOptimizationGoal"
placeholder="请选择优化目标">
<el-option label="价值优化" value="VALUE"></el-option>
<el-option label="应用安装" value="INSTALL"></el-option>
</el-select>
</el-form-item>
<el-form-item label="深度出价类型" prop="tiktok_json.deepBidType">
<el-select
v-model="tiktokDeepBidType"
placeholder="请选择深度出价类型">
<el-option label="最小ROAS" value="VO_MIN_ROAS"></el-option>
<el-option label="最高价值" value="VO_HIGHEST_VALUE"></el-option>
</el-select>
</el-form-item>
<el-form-item label="深度CPA出价" prop="tiktok_json.deepCpaBid">
<el-input-number
v-model="tiktokDeepCpaBid"
:min="0"
:precision="2"
:step="0.01">
</el-input-number>
</el-form-item>
<el-form-item label="投放位置" prop="tiktok_json.placements">
<el-select
v-model="tiktokPlacements"
multiple
placeholder="请选择投放位置">
<el-option label="TikTok" value="PLACEMENT_TIKTOK"></el-option>
<el-option label="Pangle" value="PLACEMENT_PANGLE"></el-option>
<el-option label="Global App Bundle" value="PLACEMENT_GLOBAL_APP_BUNDLE"></el-option>
</el-select>
</el-form-item>
</template>
<el-form-item label="应用组" prop="app_groups">
<app-group-selector v-model="form.app_groups" />
</el-form-item>
......@@ -287,7 +336,15 @@ export default {
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
description_groups: [],
// 初始化tiktok_json为对象而不是null
tiktok_json: {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 添加isSmart字段,默认为false
}
},
rules: {
name: [
......@@ -304,19 +361,33 @@ export default {
],
daily_budget: [
{ required: true, message: '请输入日预算', trigger: 'blur' },
{
{
validator: (rule, value, callback) => {
if (this.form.platform === 2 && value < 50) {
callback(new Error('TikTok平台日预算必须大于等于50'))
} else {
callback()
}
},
trigger: 'blur'
},
trigger: 'blur'
}
],
bidding_type: [
{ required: true, message: '请选择转化目标', trigger: 'change' }
],
// 新增TikTok特定字段的验证规则
'tiktok_json.optimizationGoal': [
{ required: true, message: '请选择优化目标', trigger: 'change' }
],
'tiktok_json.deepBidType': [
{ required: true, message: '请选择深度出价类型', trigger: 'change' }
],
'tiktok_json.deepCpaBid': [
{ required: true, message: '请输入深度CPA出价', trigger: 'blur' },
{ type: 'number', min: 0, message: '深度CPA出价必须大于0', trigger: 'blur' }
],
'tiktok_json.placements': [
{ required: true, message: '请选择投放位置', trigger: 'change' }
]
},
groupNameMaps: {
......@@ -328,6 +399,64 @@ export default {
}
}
},
computed: {
// TikTok字段的计算属性
tiktokOptimizationGoal: {
get() {
return this.form.tiktok_json ? this.form.tiktok_json.optimizationGoal : 'VALUE'
},
set(value) {
if (!this.form.tiktok_json) {
this.form.tiktok_json = {}
}
this.form.tiktok_json.optimizationGoal = value
}
},
tiktokDeepBidType: {
get() {
return this.form.tiktok_json ? this.form.tiktok_json.deepBidType : 'VO_MIN_ROAS'
},
set(value) {
if (!this.form.tiktok_json) {
this.form.tiktok_json = {}
}
this.form.tiktok_json.deepBidType = value
}
},
tiktokDeepCpaBid: {
get() {
return this.form.tiktok_json ? Number(this.form.tiktok_json.deepCpaBid) : 0
},
set(value) {
if (!this.form.tiktok_json) {
this.form.tiktok_json = {}
}
this.form.tiktok_json.deepCpaBid = Number(value)
}
},
tiktokPlacements: {
get() {
return this.form.tiktok_json ? this.form.tiktok_json.placements : []
},
set(value) {
if (!this.form.tiktok_json) {
this.form.tiktok_json = {}
}
this.form.tiktok_json.placements = value
}
},
tiktokIsSmart: {
get() {
return this.form.tiktok_json ? this.form.tiktok_json.isSmart : false
},
set(value) {
if (!this.form.tiktok_json) {
this.form.tiktok_json = {}
}
this.form.tiktok_json.isSmart = value
}
}
},
created() {
this.fetchData()
this.fetchGroupNames()
......@@ -444,22 +573,108 @@ export default {
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
description_groups: [],
// 初始化为对象而不是null
tiktok_json: {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false // 默认关闭智能创建
}
}
this.dialogVisible = true
},
// 平台变化时的处理
handlePlatformChange(value) {
// 当平台改变时,重新验证日预算字段
this.$nextTick(() => {
this.$refs.form.validateField('daily_budget');
// 如果切换到TikTok平台,初始化tiktok_json
if (value === 2) {
// 使用Vue.set确保响应式
this.$set(this.form, 'tiktok_json', {
optimizationGoal: this.tiktokOptimizationGoal,
deepBidType: this.tiktokDeepBidType,
deepCpaBid: this.tiktokDeepCpaBid,
placements: this.tiktokPlacements,
isSmart: this.tiktokIsSmart
});
} else {
// 如果切换到其他平台,保存当前值但设置为null
const currentValues = {
optimizationGoal: this.tiktokOptimizationGoal,
deepBidType: this.tiktokDeepBidType,
deepCpaBid: this.tiktokDeepCpaBid,
placements: this.tiktokPlacements,
isSmart: this.tiktokIsSmart
};
console.log('保存的TikTok配置:', currentValues);
this.$set(this.form, 'tiktok_json', null);
}
});
},
// 修改编辑方法
handleEdit(row) {
this.dialogTitle = '编辑模板'
this.form = {
...row,
app_groups: row.app_groups || [],
location_groups: row.location_groups || [],
material_groups: row.material_groups || [],
title_groups: row.title_groups || [],
description_groups: row.description_groups || []
console.log('编辑行数据:', row);
// 处理tiktok_json字段
let tiktokJsonData = {
optimizationGoal: 'VALUE',
deepBidType: 'VO_MIN_ROAS',
deepCpaBid: 0,
placements: [],
isSmart: false
};
if (row.platform === 2) {
try {
// 确保正确解析tiktok_json字段
if (row.tiktok_json) {
const parsedData = typeof row.tiktok_json === 'string' ?
JSON.parse(row.tiktok_json) : row.tiktok_json;
console.log('解析后的TikTok数据:', parsedData);
// 使用解构赋值来合并默认值和已有数据,确保类型转换
tiktokJsonData = {
optimizationGoal: parsedData.optimizationGoal || 'VALUE',
deepBidType: parsedData.deepBidType || 'VO_MIN_ROAS',
deepCpaBid: parsedData.deepCpaBid !== undefined && parsedData.deepCpaBid !== null ?
Number(parsedData.deepCpaBid) : 0,
placements: Array.isArray(parsedData.placements) ? [...parsedData.placements] : [],
isSmart: typeof parsedData.isSmart === 'boolean' ? parsedData.isSmart : false
};
}
} catch (error) {
console.error('解析TikTok配置失败:', error);
// 保持默认值
}
}
this.dialogVisible = true
// 设置表单数据
const formData = {
...row,
app_groups: Array.isArray(row.app_groups) ? [...row.app_groups] : [],
location_groups: Array.isArray(row.location_groups) ? [...row.location_groups] : [],
material_groups: Array.isArray(row.material_groups) ? [...row.material_groups] : [],
title_groups: Array.isArray(row.title_groups) ? [...row.title_groups] : [],
description_groups: Array.isArray(row.description_groups) ? [...row.description_groups] : []
};
// 使用Vue.set确保响应式更新
this.$set(this, 'form', formData);
this.$nextTick(() => {
// 在下一个tick更新tiktok_json,确保响应式
this.$set(this.form, 'tiktok_json', tiktokJsonData);
console.log('设置后的表单数据:', this.form);
});
this.dialogVisible = true;
},
async handleDelete(row) {
......@@ -467,7 +682,7 @@ export default {
await this.$confirm('确认删除该模板吗?', '提示', {
type: 'warning'
})
const response = await deleteCampaignTemplate(row.id)
if (response.status === 200) {
this.$message.success('删除成功')
......@@ -486,36 +701,55 @@ export default {
async submitForm() {
this.$refs.form.validate(async (valid) => {
if (valid) {
// 创建一个新的表单数据对象
const formData = {
...this.form,
app_groups: this.form.app_groups,
location_groups: this.form.location_groups,
material_groups: this.form.material_groups,
title_groups: this.form.title_groups,
description_groups: this.form.description_groups
app_groups: Array.isArray(this.form.app_groups) ? [...this.form.app_groups] : [],
location_groups: Array.isArray(this.form.location_groups) ? [...this.form.location_groups] : [],
material_groups: Array.isArray(this.form.material_groups) ? [...this.form.material_groups] : [],
title_groups: Array.isArray(this.form.title_groups) ? [...this.form.title_groups] : [],
description_groups: Array.isArray(this.form.description_groups) ? [...this.form.description_groups] : []
};
// 如果是TikTok平台,处理tiktok_json
if (formData.platform === 2) {
const tiktokData = {
optimizationGoal: this.tiktokOptimizationGoal,
deepBidType: this.tiktokDeepBidType,
deepCpaBid: this.tiktokDeepCpaBid,
placements: Array.isArray(this.tiktokPlacements) ? [...this.tiktokPlacements] : [],
isSmart: this.tiktokIsSmart
};
console.log('提交前的TikTok数据:', tiktokData);
formData.tiktok_json = JSON.stringify(tiktokData);
} else {
formData.tiktok_json = null;
}
console.log('提交的表单数据:', formData);
try {
let response
let response;
if (formData.id) {
response = await updateCampaignTemplate(formData)
response = await updateCampaignTemplate(formData);
} else {
response = await createCampaignTemplate(formData)
response = await createCampaignTemplate(formData);
}
if (response.status === 200) {
this.$message.success(formData.id ? '更新成功' : '创建成功')
this.dialogVisible = false
this.fetchData()
this.$message.success(formData.id ? '更新成功' : '创建成功');
this.dialogVisible = false;
this.fetchData();
} else {
this.$message.error(response.msg || (formData.id ? '更新失败' : '创建失败'))
this.$message.error(response.msg || (formData.id ? '更新失败' : '创建失败'));
}
} catch (error) {
console.error(formData.id ? '更新失败:' : '创建失败:', error)
this.$message.error(formData.id ? '更新失败' : '创建失败')
console.error(formData.id ? '更新失败:' : '创建失败:', error);
this.$message.error(formData.id ? '更新失败' : '创建失败');
}
}
})
});
},
handleAppGroupChange() {
......@@ -533,14 +767,6 @@ export default {
handleDescriptionGroupChange() {
this.fetchData()
},
// 平台变化时重新验证日预算
handlePlatformChange() {
// 当平台改变时,重新验证日预算字段
this.$nextTick(() => {
this.$refs.form.validateField('daily_budget')
})
}
}
}
</script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment