Commit c78e3ae4 authored by lijin's avatar lijin

创建人物支持临时编辑模板,增加灵活性

parent 5e53b31c
...@@ -136,13 +136,141 @@ ...@@ -136,13 +136,141 @@
</div> </div>
<!-- Add/Edit Dialog --> <!-- Add/Edit Dialog -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="50%"> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="70%">
<el-form :model="form" :rules="rules" ref="form" label-width="120px"> <el-form :model="form" :rules="rules" ref="form" label-width="120px">
<el-form-item label="计划模板" prop="campaignTemplateId"> <!-- 新增:选择模板类型 -->
<el-form-item label="模板类型" prop="templateType">
<el-radio-group v-model="form.templateType" @change="handleTemplateTypeChange">
<el-radio :label="1">使用现有模板</el-radio>
<el-radio :label="2">创建临时模板</el-radio>
</el-radio-group>
</el-form-item>
<!-- 选择现有模板 -->
<el-form-item label="计划模板" prop="campaignTemplateId" v-if="form.templateType === 1">
<campaign-template-selector <campaign-template-selector
v-model="form.campaignTemplateId" v-model="form.campaignTemplateId"
@change="handleTemplateSelectorChange"
/> />
</el-form-item> </el-form-item>
<!-- 模板详情展示区域 -->
<div v-if="form.templateType === 1 && selectedTemplate" class="template-details">
<el-divider content-position="left">模板详情(可修改)</el-divider>
<el-form-item label="模板名称">
<el-input v-model="form.tempName" :placeholder="selectedTemplate.name" disabled></el-input>
</el-form-item>
<el-form-item label="广告系列类型" prop="campaign_type">
<el-select v-model="form.campaign_type" placeholder="请选择">
<el-option label="应用安装" :value="1"></el-option>
<el-option label="应用互动" :value="2"></el-option>
<el-option label="应用预注册" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="移动应用平台" prop="appStore">
<el-select v-model="form.appStore" placeholder="请选择">
<el-option label="iOS" :value="2"></el-option>
<el-option label="Android" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="日预算($)" prop="daily_budget">
<el-input-number v-model="form.daily_budget" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="转化目标" prop="bidding_type">
<el-select v-model="form.bidding_type" placeholder="请选择">
<el-option label="安装量" :value="2" :disabled="true"></el-option>
<el-option label="应用内操作次数" :value="3" :disabled="true"></el-option>
<el-option label="应用内操作价值" :value="5"></el-option>
</el-select>
</el-form-item>
<el-form-item label="目标考核ROAS(%)" prop="target_roas">
<el-input-number v-model="form.target_roas" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="应用组" prop="app_groups">
<app-group-selector v-model="form.app_groups" />
</el-form-item>
<el-form-item label="地域组" prop="location_groups">
<location-group-selector v-model="form.location_groups" />
</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">
<title-group-selector v-model="form.title_groups" />
</el-form-item>
<el-form-item label="描述组" prop="description_groups">
<description-group-selector v-model="form.description_groups" />
</el-form-item>
</div>
<!-- 临时模板表单 -->
<template v-if="form.templateType === 2">
<el-form-item label="模板名称" prop="tempTemplateName">
<el-input v-model="form.tempTemplateName"></el-input>
</el-form-item>
<el-form-item label="广告系列类型" prop="campaign_type">
<el-select v-model="form.campaign_type" placeholder="请选择">
<el-option label="应用安装" :value="1"></el-option>
<el-option label="应用互动" :value="2"></el-option>
<el-option label="应用预注册" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="移动应用平台" prop="appStore">
<el-select v-model="form.appStore" placeholder="请选择">
<el-option label="iOS" :value="2"></el-option>
<el-option label="Android" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="日预算" prop="daily_budget">
<el-input-number v-model="form.daily_budget" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="转化目标" prop="bidding_type">
<el-select v-model="form.bidding_type" placeholder="请选择">
<el-option label="安装量" :value="2" :disabled="true"></el-option>
<el-option label="应用内操作次数" :value="3" :disabled="true"></el-option>
<el-option label="应用内操作价值" :value="5"></el-option>
</el-select>
</el-form-item>
<el-form-item label="目标考核ROAS" prop="target_roas">
<el-input-number v-model="form.target_roas" :min="0"></el-input-number>
</el-form-item>
<el-form-item label="应用组" prop="app_groups">
<app-group-selector v-model="form.app_groups" />
</el-form-item>
<el-form-item label="地域组" prop="location_groups">
<location-group-selector v-model="form.location_groups" />
</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">
<title-group-selector v-model="form.title_groups" />
</el-form-item>
<el-form-item label="描述组" prop="description_groups">
<description-group-selector v-model="form.description_groups" />
</el-form-item>
</template>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
...@@ -331,9 +459,15 @@ ...@@ -331,9 +459,15 @@
</template> </template>
<script> <script>
import { getCampaignTaskDetailById, getCampaignTaskDetails, getCampaignTaskDetailList } from '@/api/campaignTaskDetail'
import CampaignTemplateSelector from '@/components/GroupSelectors/CampaignTemplateSelector' import CampaignTemplateSelector from '@/components/GroupSelectors/CampaignTemplateSelector'
import { getCampaignTaskList, createCampaignTask, updateCampaignTask } from '@/api/campaignTask' import AppGroupSelector from '@/components/GroupSelectors/AppGroupSelector'
import { getCampaignTaskDetailById, getCampaignTaskDetails } from '@/api/campaignTaskDetail' import LocationGroupSelector from '@/components/GroupSelectors/LocationGroupSelector'
import MaterialGroupSelector from '@/components/GroupSelectors/MaterialGroupSelector'
import TitleGroupSelector from '@/components/GroupSelectors/TitleGroupSelector'
import DescriptionGroupSelector from '@/components/GroupSelectors/DescriptionGroupSelector'
import { getCampaignTaskList, createCampaignTask, updateCampaignTask, deleteCampaignTask, startCampaignTask } from '@/api/campaignTask'
import { getCampaignTemplateById } from '@/api/campaignTemplate'
import axios from 'axios' import axios from 'axios'
import moment from 'moment' import moment from 'moment'
...@@ -344,7 +478,12 @@ moment.locale('zh-cn') ...@@ -344,7 +478,12 @@ moment.locale('zh-cn')
export default { export default {
name: 'CampaignTaskManage', name: 'CampaignTaskManage',
components: { components: {
CampaignTemplateSelector CampaignTemplateSelector,
AppGroupSelector,
LocationGroupSelector,
MaterialGroupSelector,
TitleGroupSelector,
DescriptionGroupSelector
}, },
data() { data() {
const end = moment() const end = moment()
...@@ -363,11 +502,53 @@ export default { ...@@ -363,11 +502,53 @@ export default {
dialogVisible: false, dialogVisible: false,
dialogTitle: '', dialogTitle: '',
form: { form: {
campaignTemplateId: null id: null,
campaignTemplateId: null,
templateType: 1,
tempTemplateName: '',
tempName: '',
campaign_type: 1,
appStore: 2,
daily_budget: 100,
bidding_type: 5,
target_roas: 100,
app_groups: [],
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
}, },
selectedTemplate: null,
rules: { rules: {
campaignTemplateId: [ campaignTemplateId: [
{ required: true, message: '请选择计划模板', trigger: 'change' } { required: true, message: '请选择计划模板', trigger: 'change' }
],
templateType: [
{ required: true, message: '请选择模板类型', trigger: 'change' }
],
tempTemplateName: [
{ required: true, message: '请输入模板名称', trigger: 'blur' }
],
campaign_type: [
{ required: true, message: '请选择广告系列类型', trigger: 'change' }
],
appStore: [
{ required: true, message: '请选择移动应用平台', trigger: 'change' }
],
daily_budget: [
{ required: true, message: '请输入日预算', trigger: 'blur' }
],
bidding_type: [
{ required: true, message: '请选择转化目标', trigger: 'change' }
],
target_roas: [
{ required: true, message: '请输入目标考核ROAS', trigger: 'blur' }
],
app_groups: [
{ required: true, type: 'array', message: '请选择应用组', trigger: 'change' }
],
location_groups: [
{ required: true, type: 'array', message: '请选择地域组', trigger: 'change' }
] ]
}, },
templateMap: new Map(), templateMap: new Map(),
...@@ -384,11 +565,16 @@ export default { ...@@ -384,11 +565,16 @@ export default {
currentErrorMsg: '', currentErrorMsg: '',
currentTaskId: null, currentTaskId: null,
detailLoading: false, detailLoading: false,
appGroupOptions: [],
locationGroupOptions: [],
materialGroupOptions: [],
titleGroupOptions: [],
descriptionGroupOptions: [],
} }
}, },
created() { created() {
this.fetchTemplates()
this.fetchData() this.fetchData()
this.initGroupOptions()
}, },
methods: { methods: {
moment, moment,
...@@ -492,16 +678,70 @@ export default { ...@@ -492,16 +678,70 @@ export default {
showAddDialog() { showAddDialog() {
this.dialogTitle = '创建任务' this.dialogTitle = '创建任务'
this.isEdit = false
// 重置表单
this.form = { this.form = {
campaignTemplateId: null id: null,
campaignTemplateId: null,
templateType: 1,
tempTemplateName: '',
tempName: '',
campaign_type: 1,
appStore: 2,
daily_budget: 100,
bidding_type: 5,
target_roas: 100,
app_groups: [],
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
} }
this.selectedTemplate = null
this.dialogVisible = true this.dialogVisible = true
// 初始化组选项
this.initGroupOptions()
// 如果表单有验证错误,清除它们
if (this.$refs.form) {
this.$refs.form.clearValidate()
}
}, },
handleEdit(row) { handleEdit(row) {
this.dialogTitle = '编辑任务' this.dialogTitle = '编辑任务'
this.form = { ...row } this.isEdit = true
this.form = {
id: row.id,
campaignTemplateId: row.campaignTemplateId,
templateType: 1,
tempTemplateName: '',
tempName: '',
campaign_type: 1,
appStore: 2,
daily_budget: 100,
bidding_type: 5,
target_roas: 100,
app_groups: [],
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
}
// 获取模板详情
if (row.campaignTemplateId) {
this.handleTemplateSelectorChange(row.campaignTemplateId)
}
this.dialogVisible = true this.dialogVisible = true
// 初始化组选项
this.initGroupOptions()
}, },
handleDetail(row) { handleDetail(row) {
...@@ -560,32 +800,140 @@ export default { ...@@ -560,32 +800,140 @@ export default {
console.log('重试任务:', row) console.log('重试任务:', row)
}, },
async submitForm() { submitForm() {
this.$refs.form.validate(async (valid) => { this.$refs.form.validate(async valid => {
if (valid) { if (valid) {
const loading = this.$loading({
lock: true,
text: '提交中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try { try {
let taskData = {}
if (this.form.templateType === 1) {
// 使用现有模板
// 检查是否修改了模板配置
const templateModified = this.isTemplateModified()
if (templateModified) {
// 创建一个修改后的临时模板
const modifiedTemplate = {
name: this.selectedTemplate.name + ' (修改版)',
campaign_type: this.form.campaign_type,
appStore: this.form.appStore,
daily_budget: this.form.daily_budget,
bidding_type: this.form.bidding_type,
target_roas: this.form.target_roas,
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,
is_temp: 1, // 标记为临时模板
original_template_id: this.form.campaignTemplateId // 记录原始模板ID
}
taskData = {
id: this.form.id,
tempTemplate: modifiedTemplate,
useModifiedTemplate: true
}
} else {
// 没有修改模板,直接使用原模板
taskData = {
id: this.form.id,
campaignTemplateId: this.form.campaignTemplateId
}
}
} else {
// 使用临时模板,创建临时模板并使用
const templateData = {
name: this.form.tempTemplateName,
campaign_type: this.form.campaign_type,
appStore: this.form.appStore,
daily_budget: this.form.daily_budget,
bidding_type: this.form.bidding_type,
target_roas: this.form.target_roas,
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,
is_temp: 1 // 标记为临时模板
}
taskData = {
id: this.form.id,
tempTemplate: templateData
}
}
let response let response
if (this.form.id) { if (this.isEdit) {
response = await updateCampaignTask(this.form) response = await updateCampaignTask(taskData)
} else { } else {
response = await createCampaignTask(this.form) response = await createCampaignTask(taskData)
} }
if (response.status === 200) { if (response.data && response.data.code === 200) {
this.$message.success(this.form.id ? '更新成功' : '创建成功') this.$message.success(this.isEdit ? '更新成功' : '创建成功')
this.dialogVisible = false this.dialogVisible = false
this.fetchData() this.fetchData()
} else { } else {
this.$message.error(response.msg || (this.form.id ? '更新失败' : '创建失败')) this.$message.error(response.data.message || '操作失败')
} }
} catch (error) { } catch (error) {
console.error(this.form.id ? '更新失败:' : '创建失败:', error) console.error('提交失败:', error)
this.$message.error(this.form.id ? '更新失败' : '创建失败') this.$message.error('提交失败')
} finally {
loading.close()
} }
} }
}) })
}, },
// 检查模板是否被修改
isTemplateModified() {
if (!this.selectedTemplate) return false
const template = this.selectedTemplate
// 比较各个字段值是否有变化
if (template.campaign_type !== this.form.campaign_type) return true
if (template.appStore !== this.form.appStore) return true
if (template.daily_budget !== this.form.daily_budget) return true
if (template.bidding_type !== this.form.bidding_type) return true
if (template.target_roas !== this.form.target_roas) return true
// 比较数组是否有变化
const compareArrays = (arr1, arr2) => {
if (!arr1 && !arr2) return true
if (!arr1 || !arr2) return false
if (arr1.length !== arr2.length) return false
// 排序后比较各元素
const sorted1 = [...arr1].sort()
const sorted2 = [...arr2].sort()
for (let i = 0; i < sorted1.length; i++) {
if (sorted1[i] !== sorted2[i]) return false
}
return true
}
if (!compareArrays(template.app_groups, this.form.app_groups)) return true
if (!compareArrays(template.location_groups, this.form.location_groups)) return true
if (!compareArrays(template.material_groups, this.form.material_groups)) return true
if (!compareArrays(template.title_groups, this.form.title_groups)) return true
if (!compareArrays(template.description_groups, this.form.description_groups)) return true
return false
},
showErrorReason() { showErrorReason() {
this.errorReasonVisible = true; this.errorReasonVisible = true;
}, },
...@@ -640,14 +988,146 @@ export default { ...@@ -640,14 +988,146 @@ export default {
} else { } else {
this.$message.info('没有失败原因'); this.$message.info('没有失败原因');
} }
} },
// 切换模板类型
handleTemplateTypeChange(value) {
this.form.templateType = value
if (value === 1) {
// 使用现有模板,清空临时模板数据
this.form.tempTemplateName = ''
// 保留其他表单数据,以便在修改时使用
} else {
// 使用临时模板,清空选择的现有模板
this.form.campaignTemplateId = null
this.selectedTemplate = null
// 当切换到临时模板时,保留之前填写的数据(如果有)或使用默认值
if (!this.form.tempTemplateName) {
// 如果选择过模板后切换到临时模板,使用模板名称+"(临时)"作为默认名称
if (this.form.tempName) {
this.form.tempTemplateName = this.form.tempName + ' (临时)'
}
}
}
},
// 选择模板后的处理
async handleTemplateSelectorChange(templateId) {
if (!templateId) {
this.selectedTemplate = null
return
}
try {
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 || [])]
}
} catch (error) {
console.error('获取模板详情失败:', error)
this.$message.error('获取模板详情失败')
}
},
// 应用组名称获取
getAppGroupName(id) {
const group = this.appGroupOptions.find(item => item.id === id)
return group ? group.name : id
},
// 地域组名称获取
getLocationGroupName(id) {
const group = this.locationGroupOptions.find(item => item.id === id)
return group ? group.name : id
},
// 素材组名称获取
getMaterialGroupName(id) {
const group = this.materialGroupOptions.find(item => item.id === id)
return group ? group.name : id
},
// 标题组名称获取
getTitleGroupName(id) {
const group = this.titleGroupOptions.find(item => item.id === id)
return group ? group.name : id
},
// 描述组名称获取
getDescriptionGroupName(id) {
const group = this.descriptionGroupOptions.find(item => item.id === id)
return group ? group.name : id
},
// 初始化模板选择相关数据
async initGroupOptions() {
// 初始化所有组选项
this.appGroupOptions = []
this.locationGroupOptions = []
this.materialGroupOptions = []
this.titleGroupOptions = []
this.descriptionGroupOptions = []
try {
// 获取应用组数据
const appGroupRes = await axios.get(process.env.PUTIN_API + '/app-groups')
if (appGroupRes.data && appGroupRes.data.code === 200 && appGroupRes.data.data) {
this.appGroupOptions = appGroupRes.data.data
}
// 获取地域组数据
const locationGroupRes = await axios.get(process.env.PUTIN_API + '/location-groups')
if (locationGroupRes.data && locationGroupRes.data.code === 200 && locationGroupRes.data.data) {
this.locationGroupOptions = locationGroupRes.data.data
}
// 获取素材组数据
const materialGroupRes = await axios.get(process.env.PUTIN_API + '/material-groups')
if (materialGroupRes.data && materialGroupRes.data.code === 200 && materialGroupRes.data.data) {
this.materialGroupOptions = materialGroupRes.data.data
}
// 获取标题组数据
const titleGroupRes = await axios.get(process.env.PUTIN_API + '/title-groups')
if (titleGroupRes.data && titleGroupRes.data.code === 200 && titleGroupRes.data.data) {
this.titleGroupOptions = titleGroupRes.data.data
}
// 获取描述组数据
const descriptionGroupRes = await axios.get(process.env.PUTIN_API + '/description-groups')
if (descriptionGroupRes.data && descriptionGroupRes.data.code === 200 && descriptionGroupRes.data.data) {
this.descriptionGroupOptions = descriptionGroupRes.data.data
}
} catch (error) {
console.error('获取组选项数据失败:', error)
this.$message.warning('获取组选项数据失败,部分组信息可能无法正确显示')
}
},
} }
} }
</script> </script>
<style scoped> <style lang="scss" scoped>
.campaign-task-container { .campaign-task-container {
padding: 20px; padding: 20px;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
} }
.filter-section { .filter-section {
...@@ -710,7 +1190,6 @@ export default { ...@@ -710,7 +1190,6 @@ export default {
} }
.pagination-container { .pagination-container {
margin-top: 20px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
padding: 10px 0; padding: 10px 0;
...@@ -892,12 +1371,6 @@ export default { ...@@ -892,12 +1371,6 @@ export default {
color: #909399; color: #909399;
} }
.more-countries {
color: #909399;
font-style: italic;
margin-left: 5px;
}
.no-countries { .no-countries {
color: #909399; color: #909399;
font-style: italic; font-style: italic;
...@@ -906,13 +1379,12 @@ export default { ...@@ -906,13 +1379,12 @@ export default {
.table-countries-container { .table-countries-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 5px; justify-content: center;
max-width: 100%;
} }
.no-countries-text { .no-countries-text {
color: #909399; color: #909399;
font-style: italic; font-size: 12px;
} }
/* 调整标签在表格中的样式 */ /* 调整标签在表格中的样式 */
...@@ -950,11 +1422,11 @@ export default { ...@@ -950,11 +1422,11 @@ export default {
.operation-buttons { .operation-buttons {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 8px; align-items: center;
} }
.operation-buttons .el-button { .operation-buttons .el-button {
margin-left: 0; margin: 0 3px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease; transition: all 0.3s ease;
} }
...@@ -963,4 +1435,30 @@ export default { ...@@ -963,4 +1435,30 @@ export default {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
} }
// 模板详情样式
.template-details {
margin: 15px 0;
padding: 15px;
border: 1px solid #EBEEF5;
border-radius: 4px;
background-color: #f9f9f9;
.el-divider {
margin-top: 0;
}
.el-form-item {
margin-bottom: 12px;
}
.group-tags {
display: flex;
flex-wrap: wrap;
.el-tag {
margin: 2px 5px 2px 0;
}
}
}
</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