Commit c78e3ae4 authored by lijin's avatar lijin

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

parent 5e53b31c
......@@ -136,13 +136,141 @@
</div>
<!-- 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-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
v-model="form.campaignTemplateId"
@change="handleTemplateSelectorChange"
/>
</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>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
......@@ -331,9 +459,15 @@
</template>
<script>
import { getCampaignTaskDetailById, getCampaignTaskDetails, getCampaignTaskDetailList } from '@/api/campaignTaskDetail'
import CampaignTemplateSelector from '@/components/GroupSelectors/CampaignTemplateSelector'
import { getCampaignTaskList, createCampaignTask, updateCampaignTask } from '@/api/campaignTask'
import { getCampaignTaskDetailById, getCampaignTaskDetails } from '@/api/campaignTaskDetail'
import AppGroupSelector from '@/components/GroupSelectors/AppGroupSelector'
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 moment from 'moment'
......@@ -344,7 +478,12 @@ moment.locale('zh-cn')
export default {
name: 'CampaignTaskManage',
components: {
CampaignTemplateSelector
CampaignTemplateSelector,
AppGroupSelector,
LocationGroupSelector,
MaterialGroupSelector,
TitleGroupSelector,
DescriptionGroupSelector
},
data() {
const end = moment()
......@@ -363,11 +502,53 @@ export default {
dialogVisible: false,
dialogTitle: '',
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: {
campaignTemplateId: [
{ 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(),
......@@ -384,11 +565,16 @@ export default {
currentErrorMsg: '',
currentTaskId: null,
detailLoading: false,
appGroupOptions: [],
locationGroupOptions: [],
materialGroupOptions: [],
titleGroupOptions: [],
descriptionGroupOptions: [],
}
},
created() {
this.fetchTemplates()
this.fetchData()
this.initGroupOptions()
},
methods: {
moment,
......@@ -492,16 +678,70 @@ export default {
showAddDialog() {
this.dialogTitle = '创建任务'
this.isEdit = false
// 重置表单
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.initGroupOptions()
// 如果表单有验证错误,清除它们
if (this.$refs.form) {
this.$refs.form.clearValidate()
}
},
handleEdit(row) {
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.initGroupOptions()
},
handleDetail(row) {
......@@ -560,32 +800,140 @@ export default {
console.log('重试任务:', row)
},
async submitForm() {
this.$refs.form.validate(async (valid) => {
submitForm() {
this.$refs.form.validate(async valid => {
if (valid) {
const loading = this.$loading({
lock: true,
text: '提交中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
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
if (this.form.id) {
response = await updateCampaignTask(this.form)
if (this.isEdit) {
response = await updateCampaignTask(taskData)
} else {
response = await createCampaignTask(this.form)
response = await createCampaignTask(taskData)
}
if (response.status === 200) {
this.$message.success(this.form.id ? '更新成功' : '创建成功')
if (response.data && response.data.code === 200) {
this.$message.success(this.isEdit ? '更新成功' : '创建成功')
this.dialogVisible = false
this.fetchData()
} else {
this.$message.error(response.msg || (this.form.id ? '更新失败' : '创建失败'))
this.$message.error(response.data.message || '操作失败')
}
} catch (error) {
console.error(this.form.id ? '更新失败:' : '创建失败:', error)
this.$message.error(this.form.id ? '更新失败' : '创建失败')
console.error('提交失败:', error)
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() {
this.errorReasonVisible = true;
},
......@@ -640,14 +988,146 @@ export default {
} else {
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>
<style scoped>
<style lang="scss" scoped>
.campaign-task-container {
padding: 20px;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.filter-section {
......@@ -710,7 +1190,6 @@ export default {
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
padding: 10px 0;
......@@ -892,12 +1371,6 @@ export default {
color: #909399;
}
.more-countries {
color: #909399;
font-style: italic;
margin-left: 5px;
}
.no-countries {
color: #909399;
font-style: italic;
......@@ -906,13 +1379,12 @@ export default {
.table-countries-container {
display: flex;
flex-wrap: wrap;
gap: 5px;
max-width: 100%;
justify-content: center;
}
.no-countries-text {
color: #909399;
font-style: italic;
font-size: 12px;
}
/* 调整标签在表格中的样式 */
......@@ -950,11 +1422,11 @@ export default {
.operation-buttons {
display: flex;
justify-content: center;
gap: 8px;
align-items: center;
}
.operation-buttons .el-button {
margin-left: 0;
margin: 0 3px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
......@@ -963,4 +1435,30 @@ export default {
transform: translateY(-2px);
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>
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