Commit 75ea1aff authored by hzl's avatar hzl

feat: 多计划处理

parent da148f0b
...@@ -74,6 +74,12 @@ ...@@ -74,6 +74,12 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="accountName" label="账户名称" width="150">
<template slot-scope="scope">
{{ scope.row.accountName || '-' }}
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="180"> <el-table-column prop="startTime" label="开始时间" width="180">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.startTime ? moment(scope.row.startTime).format('YYYY-MM-DD HH:mm:ss') : '' }} {{ scope.row.startTime ? moment(scope.row.startTime).format('YYYY-MM-DD HH:mm:ss') : '' }}
...@@ -299,6 +305,7 @@ ...@@ -299,6 +305,7 @@
filterable filterable
placeholder="请输入计划名称搜索" placeholder="请输入计划名称搜索"
:loading="campaignListLoading" :loading="campaignListLoading"
@change="handleCampaignSelectionChange"
style="width: 100%"> style="width: 100%">
<el-option <el-option
v-for="item in campaignOptions" v-for="item in campaignOptions"
...@@ -312,6 +319,94 @@ ...@@ -312,6 +319,94 @@
</el-form-item> </el-form-item>
</template> </template>
<!-- 多计划独立配置区域 -->
<div v-if="!form.tiktok_json.isNewCampaign && form.tiktok_json.campaignIdList && form.tiktok_json.campaignIdList.length > 0" class="multi-campaign-config">
<el-divider content-position="left">计划独立配置</el-divider>
<div class="campaign-config-list">
<div
v-for="(campaignId, index) in form.tiktok_json.campaignIdList"
:key="campaignId"
class="campaign-config-item">
<div class="campaign-config-header">
<h4>{{ getCampaignName(campaignId) }}</h4>
<el-button
type="danger"
size="mini"
icon="el-icon-delete"
@click="removeCampaignConfig(index)"
v-if="form.tiktok_json.campaignIdList.length > 1">
移除
</el-button>
</div>
<el-form :model="campaignConfigs[index]" label-width="120px" size="small">
<!-- ROAS目标值 -->
<el-form-item label="ROAS目标值" v-if="form.tiktok_json.optimizationGoal === 'VALUE' && form.tiktok_json.deepBidType === 'VO_MIN_ROAS'">
<el-input-number
v-model="campaignConfigs[index].roasBid"
:min="0.01"
:max="1000"
:precision="2"
placeholder="请输入ROAS目标值"
style="width: 200px;">
</el-input-number>
</el-form-item>
<!-- 日预算 -->
<el-form-item label="日预算($)">
<el-input-number
v-model="campaignConfigs[index].dailyBudget"
:min="20"
placeholder="请输入日预算"
style="width: 200px;">
</el-input-number>
</el-form-item>
<!-- 地域组 -->
<el-form-item label="地域组">
<location-group-selector
v-model="campaignConfigs[index].locationGroups"
@change="handleCampaignLocationGroupsChange(index, $event)" />
<!-- 展示已选地域组列表 -->
<div v-if="campaignConfigs[index].locationGroups && campaignConfigs[index].locationGroups.length > 0" class="group-detail-container">
<div class="group-detail-title">已选地域组列表</div>
<el-table :data="getCampaignLocationGroupData(index)" size="mini" border style="width: 100%">
<el-table-column prop="name" label="地域组名称"></el-table-column>
<el-table-column prop="id" label="地域组ID"></el-table-column>
<el-table-column label="国家数量">
<template slot-scope="scope">
{{ getLocationGroupCountries(scope.row).length }} 个国家
</template>
</el-table-column>
<el-table-column label="计划数" width="200" align="center">
<template slot-scope="scope">
<el-input-number v-model="scope.row.campaignCount" :min="1" @change="updateCampaignLocationGroupCampaignCount(index, scope.row, $event)" size="mini"></el-input-number>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
<!-- 标题组 -->
<el-form-item label="标题组">
<title-group-selector v-model="campaignConfigs[index].titleGroups" />
</el-form-item>
<!-- 素材组 -->
<el-form-item label="素材组">
<material-group-selector v-model="campaignConfigs[index].materialGroups" />
</el-form-item>
<!-- 资源组 -->
<el-form-item label="资源组">
<resource-group-selector v-model="campaignConfigs[index].resourceGroups" />
</el-form-item>
</el-form>
</div>
</div>
</div>
<el-form-item <el-form-item
label="设备机型" label="设备机型"
prop="tiktok_json.deviceModels" prop="tiktok_json.deviceModels"
...@@ -366,7 +461,8 @@ ...@@ -366,7 +461,8 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="日预算($)" prop="daily_budget"> <!-- 日预算(当没有多计划自定义时显示) -->
<el-form-item label="日预算($)" prop="daily_budget" v-if="form.tiktok_json.isNewCampaign || !form.tiktok_json.campaignIdList || form.tiktok_json.campaignIdList.length === 0">
<el-input-number v-model="form.daily_budget" :min="0"></el-input-number> <el-input-number v-model="form.daily_budget" :min="0"></el-input-number>
</el-form-item> </el-form-item>
...@@ -408,7 +504,8 @@ ...@@ -408,7 +504,8 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="地域组" prop="location_groups"> <!-- 地域组(当没有多计划自定义时显示) -->
<el-form-item label="地域组" prop="location_groups" v-if="form.tiktok_json.isNewCampaign || !form.tiktok_json.campaignIdList || form.tiktok_json.campaignIdList.length === 0">
<location-group-selector v-model="form.location_groups" @change="handleLocationGroupsChange" /> <location-group-selector v-model="form.location_groups" @change="handleLocationGroupsChange" />
<!-- 展示已选地域组列表 --> <!-- 展示已选地域组列表 -->
<div v-if="form.location_groups && form.location_groups.length > 0" class="group-detail-container"> <div v-if="form.location_groups && form.location_groups.length > 0" class="group-detail-container">
...@@ -430,21 +527,24 @@ ...@@ -430,21 +527,24 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="素材组" prop="material_groups"> <!-- 素材组、标题组、描述组、资源组(当没有多计划自定义时显示) -->
<material-group-selector v-model="form.material_groups" /> <template v-if="form.tiktok_json.isNewCampaign || !form.tiktok_json.campaignIdList || form.tiktok_json.campaignIdList.length === 0">
</el-form-item> <el-form-item label="素材组" prop="material_groups">
<material-group-selector v-model="form.material_groups" />
</el-form-item>
<el-form-item label="标题组" prop="title_groups"> <el-form-item label="标题组" prop="title_groups">
<title-group-selector v-model="form.title_groups" /> <title-group-selector v-model="form.title_groups" />
</el-form-item> </el-form-item>
<el-form-item label="描述组" prop="description_groups"> <el-form-item label="描述组" prop="description_groups">
<description-group-selector v-model="form.description_groups" /> <description-group-selector v-model="form.description_groups" />
</el-form-item> </el-form-item>
<el-form-item label="资源组" prop="resource_groups"> <el-form-item label="资源组" prop="resource_groups">
<resource-group-selector v-model="form.resource_groups" /> <resource-group-selector v-model="form.resource_groups" />
</el-form-item> </el-form-item>
</template>
</div> </div>
<!-- 临时模板表单 --> <!-- 临时模板表单 -->
...@@ -789,6 +889,7 @@ export default { ...@@ -789,6 +889,7 @@ export default {
title_groups: [], title_groups: [],
description_groups: [], description_groups: [],
resource_groups: [], resource_groups: [],
accountName: '', // 新增:账户名称字段
// 修改字段名为tiktok_json // 修改字段名为tiktok_json
tiktok_json: { tiktok_json: {
optimizationGoal: 'VALUE', optimizationGoal: 'VALUE',
...@@ -992,6 +1093,10 @@ export default { ...@@ -992,6 +1093,10 @@ export default {
{ value: '12.0', label: 'Android 12.0' }, { value: '12.0', label: 'Android 12.0' },
{ value: '13.0', label: 'Android 13.0' } { value: '13.0', label: 'Android 13.0' }
], ],
// 多计划配置数据
campaignConfigs: [],
// 每个计划的地域组数据
campaignLocationGroups: [],
} }
}, },
created() { created() {
...@@ -1120,6 +1225,7 @@ export default { ...@@ -1120,6 +1225,7 @@ export default {
title_groups: [], title_groups: [],
description_groups: [], description_groups: [],
resource_groups: [], resource_groups: [],
accountName: '', // 重置账户名称
// 修改字段名为tiktok_json // 修改字段名为tiktok_json
tiktok_json: { tiktok_json: {
optimizationGoal: 'VALUE', optimizationGoal: 'VALUE',
...@@ -1171,6 +1277,7 @@ export default { ...@@ -1171,6 +1277,7 @@ export default {
title_groups: [], title_groups: [],
description_groups: [], description_groups: [],
resource_groups: [], resource_groups: [],
accountName: '', // 重置账户名称
// 修改字段名为tiktok_json // 修改字段名为tiktok_json
tiktok_json: { tiktok_json: {
optimizationGoal: 'VALUE', optimizationGoal: 'VALUE',
...@@ -1281,6 +1388,7 @@ export default { ...@@ -1281,6 +1388,7 @@ export default {
bidding_type: this.form.bidding_type, bidding_type: this.form.bidding_type,
target_roas: this.form.target_roas, target_roas: this.form.target_roas,
resource_groups: this.form.resource_groups, resource_groups: this.form.resource_groups,
accountName: this.form.accountName, // 添加账户名称
} }
// 根据模板类型传递不同的字段 // 根据模板类型传递不同的字段
...@@ -1327,20 +1435,91 @@ export default { ...@@ -1327,20 +1435,91 @@ export default {
taskData.descriptionGroups = this.form.description_groups; taskData.descriptionGroups = this.form.description_groups;
taskData.materialGroups = this.form.material_groups; taskData.materialGroups = this.form.material_groups;
let response // 检查是否是多计划配置
if (this.isEdit) { if (this.form.platform === 2 && !this.form.tiktok_json.isNewCampaign && this.campaignConfigs.length > 0) {
response = await updateCampaignTask(taskData) // 多计划配置,为每个计划单独调用接口
} else { const results = [];
response = await createCampaignTask(taskData) for (let i = 0; i < this.campaignConfigs.length; i++) {
} const campaignConfig = this.campaignConfigs[i];
console.log('提交结果:', response) const campaignTaskData = {
...taskData,
// 使用计划特定的配置
daily_budget: campaignConfig.dailyBudget,
target_roas: campaignConfig.roasBid,
location_groups: campaignConfig.locationGroups,
title_groups: campaignConfig.titleGroups,
material_groups: campaignConfig.materialGroups,
resource_groups: campaignConfig.resourceGroups,
accountName: this.form.accountName, // 添加账户名称
// 更新tiktok_json中的campaignIdList为单个计划
tiktok_json: JSON.stringify({
...this.form.tiktok_json,
campaignIdList: [campaignConfig.campaignId]
}),
// 设置单个计划名称
campaignNameList: this.getCampaignName(campaignConfig.campaignId)
};
// 使用计划特定的地域组数据
if (this.campaignLocationGroups[i] && this.campaignLocationGroups[i].length > 0) {
campaignTaskData.locationGroups = this.campaignLocationGroups[i].map(group => ({
locationGroupId: group.id,
campaignCount: group.campaignCount || 1
}));
}
// 使用全局应用数据(所有计划共用)
campaignTaskData.apps = this.appListData.map(app => ({
pkg: app.appId,
campaignCount: app.campaignCount || 1
}));
let response;
if (this.isEdit) {
response = await updateCampaignTask(campaignTaskData);
} else {
response = await createCampaignTask(campaignTaskData);
}
results.push({
campaignName: this.getCampaignName(campaignConfig.campaignId),
success: response.status === 200,
response: response
});
}
// 检查所有结果
const successCount = results.filter(r => r.success).length;
const totalCount = results.length;
if (response.status === 200) { if (successCount === totalCount) {
this.$message.success(this.isEdit ? '更新成功' : '创建成功') this.$message.success(`所有计划创建成功 (${successCount}/${totalCount})`);
this.dialogVisible = false } else {
this.fetchData() this.$message.warning(`部分计划创建成功 (${successCount}/${totalCount})`);
// 显示失败的计划
const failedCampaigns = results.filter(r => !r.success).map(r => r.campaignName);
this.$message.error(`失败的计划: ${failedCampaigns.join(', ')}`);
}
this.dialogVisible = false;
this.fetchData();
} else { } else {
this.$message.error(response.msg || '操作失败') // 单计划或新建计划,使用原有逻辑
let response;
if (this.isEdit) {
response = await updateCampaignTask(taskData);
} else {
response = await createCampaignTask(taskData);
}
console.log('提交结果:', response);
if (response.status === 200) {
this.$message.success(this.isEdit ? '更新成功' : '创建成功');
this.dialogVisible = false;
this.fetchData();
} else {
this.$message.error(response.msg || '操作失败');
}
} }
} catch (error) { } catch (error) {
console.error('提交失败:', error) console.error('提交失败:', error)
...@@ -1545,6 +1724,8 @@ export default { ...@@ -1545,6 +1724,8 @@ export default {
item => item.advertiserId === this.form.tiktok_json.advertiserId item => item.advertiserId === this.form.tiktok_json.advertiserId
); );
if (selectedAdvertiser) { if (selectedAdvertiser) {
// 设置账户名称
this.form.accountName = selectedAdvertiser.advertiserName;
// 触发账户变化,加载对应的计划列表和设备机型列表,但保留设备机型数据 // 触发账户变化,加载对应的计划列表和设备机型列表,但保留设备机型数据
this.handleAdvertiserChange(this.form.tiktok_json.advertiserId, true); this.handleAdvertiserChange(this.form.tiktok_json.advertiserId, true);
} }
...@@ -1602,33 +1783,23 @@ export default { ...@@ -1602,33 +1783,23 @@ export default {
this.form.tiktok_json.campaignIdList = []; this.form.tiktok_json.campaignIdList = [];
this.deviceModelOptions = []; this.deviceModelOptions = [];
this.form.tiktok_json.deviceModels = []; this.form.tiktok_json.deviceModels = [];
this.form.accountName = ''; // 清空账户名称
return; return;
} }
// 保存当前选中的设备机型 // 设置账户名称
const currentDeviceModels = [...this.form.tiktok_json.deviceModels]; const selectedAdvertiser = this.advertiserOptions.find(item => item.advertiserId === advertiserId);
if (selectedAdvertiser) {
this.form.accountName = selectedAdvertiser.advertiserName;
}
// 只有在不保留设备机型的情况下才清空(用户主动切换账户时)
if (!preserveDeviceModels) {
this.form.tiktok_json.deviceModels = [];
}
await this.fetchCampaignList(advertiserId); await this.fetchCampaignList(advertiserId);
await this.fetchDeviceModelList(advertiserId); await this.fetchDeviceModelList(advertiserId);
// 如果之前有选中的设备机型,且新账户的设备机型列表中包含这些机型,则保持选中状态
if (currentDeviceModels.length > 0) {
this.$nextTick(() => {
const availableDeviceModels = this.deviceModelOptions.map(item => item.device_model_id);
const validDeviceModels = currentDeviceModels.filter(modelId =>
availableDeviceModels.includes(modelId)
);
if (validDeviceModels.length > 0) {
this.form.tiktok_json.deviceModels = validDeviceModels;
console.log('保持设备机型选择:', validDeviceModels);
} else {
// 如果之前的设备机型在新账户中不可用,则清空
this.form.tiktok_json.deviceModels = [];
console.log('设备机型在新账户中不可用,已清空');
}
});
}
}, },
// 修改搜索处理方法 // 修改搜索处理方法
...@@ -2063,6 +2234,109 @@ export default { ...@@ -2063,6 +2234,109 @@ export default {
this.addAppDialogVisible = false; this.addAppDialogVisible = false;
this.selectedAppId = ''; this.selectedAppId = '';
}, },
// 获取计划名称
getCampaignName(campaignId) {
const campaign = this.campaignOptions.find(item => item.campaignId === campaignId);
return campaign ? campaign.campaignName : campaignId;
},
// 处理计划选择变化
handleCampaignSelectionChange(selectedCampaignIds) {
// 初始化每个计划的配置
this.campaignConfigs = selectedCampaignIds.map(campaignId => {
return {
campaignId: campaignId,
roasBid: this.form.tiktok_json.roasBid || 0,
dailyBudget: this.form.daily_budget || 100,
locationGroups: [...this.form.location_groups],
titleGroups: [...this.form.title_groups],
materialGroups: [...this.form.material_groups],
resourceGroups: [...this.form.resource_groups]
};
});
// 初始化每个计划的地域组数据,使用当前已选地域组的详细数据
this.campaignLocationGroups = selectedCampaignIds.map(() => {
// 复制当前已选地域组的详细数据
return this.selectedLocationGroups.map(group => ({
id: group.id,
name: group.name,
campaignCount: group.campaignCount || 1,
countryCodes: group.countryCodes || []
}));
});
},
// 移除计划配置
removeCampaignConfig(index) {
this.form.tiktok_json.campaignIdList.splice(index, 1);
this.campaignConfigs.splice(index, 1);
this.campaignLocationGroups.splice(index, 1);
},
// 处理计划地域组变化
handleCampaignLocationGroupsChange(campaignIndex, locationGroupIds) {
if (locationGroupIds && locationGroupIds.length > 0) {
this.fetchCampaignLocationGroups(campaignIndex, locationGroupIds);
} else {
this.campaignLocationGroups[campaignIndex] = [];
}
},
// 获取计划地域组数据
async fetchCampaignLocationGroups(campaignIndex, locationGroupIds) {
try {
this.campaignLocationGroups[campaignIndex] = locationGroupIds.map(id => {
const numId = Number(id);
const group = this.locationGroupOptions.find(item => Number(item.id) === numId);
if (group) {
return {
id: group.id,
name: group.name || `地域组 ${id}`,
campaignCount: 1,
countryCodes: group.countryCodes || []
};
} else {
return {
id: id,
name: `地域组 ${id}`,
campaignCount: 1,
countryCodes: []
};
}
});
} catch (error) {
console.error('获取计划地域组数据失败:', error);
this.$message.error('获取计划地域组数据失败');
}
},
// 获取计划地域组数据用于显示
getCampaignLocationGroupData(campaignIndex) {
return this.campaignLocationGroups[campaignIndex] || [];
},
// 更新计划地域组的计划数
updateCampaignLocationGroupCampaignCount(campaignIndex, group, value) {
// 验证输入值
if (value < 1) {
this.$message.warning('计划数最小为1');
// 重置为1
const index = this.campaignLocationGroups[campaignIndex].findIndex(item => item.id === group.id);
if (index !== -1) {
this.campaignLocationGroups[campaignIndex][index].campaignCount = 1;
}
return;
}
// 更新计划数
const index = this.campaignLocationGroups[campaignIndex].findIndex(item => item.id === group.id);
if (index !== -1) {
this.campaignLocationGroups[campaignIndex][index].campaignCount = value;
}
},
} }
} }
</script> </script>
...@@ -2465,4 +2739,76 @@ export default { ...@@ -2465,4 +2739,76 @@ export default {
border-color: #7dd3fc; border-color: #7dd3fc;
transform: translateX(2px); transform: translateX(2px);
} }
/* 多计划配置样式 */
.multi-campaign-config {
margin-top: 20px;
padding: 20px;
background-color: #f8f9fc;
border-radius: 6px;
border: 1px solid #e4e7ed;
}
.campaign-config-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.campaign-config-item {
background-color: #fff;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.campaign-config-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e4e7ed;
}
.campaign-config-header h4 {
margin: 0;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.campaign-config-item .el-form-item {
margin-bottom: 15px;
}
.campaign-config-item .el-form-item__label {
font-weight: 500;
color: #606266;
}
.campaign-config-item .el-input-number {
width: 200px;
}
.campaign-config-item .el-select {
width: 100%;
}
/* 共用配置信息样式 */
.shared-config-info {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background-color: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 4px;
}
.config-detail {
color: #0369a1;
font-size: 13px;
}
</style> </style>
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