Commit 12f14c4b authored by lijin's avatar lijin

增加计划模板管理页面

parent 15fa7902
import request from '@/utils/request'
export function getCampaignTemplateList() {
return request({
url: process.env.PUTIN_API + '/campaign-templates',
method: 'get'
})
}
export function getCampaignTemplateById(id) {
return request({
url: process.env.PUTIN_API + `/campaign-templates/${id}`,
method: 'get'
})
}
export function createCampaignTemplate(data) {
return request({
url: process.env.PUTIN_API + '/campaign-templates',
method: 'post',
data
})
}
export function updateCampaignTemplate(data) {
return request({
url: process.env.PUTIN_API + '/campaign-templates',
method: 'put',
data
})
}
export function deleteCampaignTemplate(id) {
return request({
url: process.env.PUTIN_API + `/campaign-templates/${id}`,
method: 'delete'
})
}
<template>
<el-select
v-model="selectedValues"
multiple
filterable
clearable
placeholder="请选择应用组"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
<script>
import axios from 'axios'
export default {
name: 'AppGroupSelector',
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
options: [],
selectedValues: []
}
},
watch: {
value: {
handler(newVal) {
this.selectedValues = newVal
},
immediate: true
}
},
created() {
this.fetchOptions()
},
methods: {
async fetchOptions() {
try {
const response = await axios.get(process.env.PUTIN_API + '/app-groups')
this.options = response.data
} catch (error) {
console.error('Failed to fetch app groups:', error)
}
},
handleChange(values) {
this.$emit('input', values)
this.$emit('change', values)
}
}
}
</script>
<template>
<el-select
v-model="selectedValues"
multiple
filterable
clearable
placeholder="请选择描述组"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
<script>
import axios from 'axios'
export default {
name: 'DescriptionGroupSelector',
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
options: [],
selectedValues: []
}
},
watch: {
value: {
handler(newVal) {
this.selectedValues = newVal
},
immediate: true
}
},
created() {
this.fetchOptions()
},
methods: {
async fetchOptions() {
try {
const response = await axios.get('http://localhost:8567/description-groups')
this.options = response.data
} catch (error) {
console.error('Failed to fetch description groups:', error)
}
},
handleChange(values) {
this.$emit('input', values)
this.$emit('change', values)
}
}
}
</script>
<template>
<el-select
v-model="selectedValues"
multiple
filterable
clearable
placeholder="请选择地域组"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
<script>
import axios from 'axios'
export default {
name: 'LocationGroupSelector',
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
options: [],
selectedValues: []
}
},
watch: {
value: {
handler(newVal) {
this.selectedValues = newVal
},
immediate: true
}
},
created() {
this.fetchOptions()
},
methods: {
async fetchOptions() {
try {
const response = await axios.get('http://localhost:8567/location-groups')
this.options = response.data
} catch (error) {
console.error('Failed to fetch location groups:', error)
}
},
handleChange(values) {
this.$emit('input', values)
this.$emit('change', values)
}
}
}
</script>
<template>
<el-select
v-model="selectedValues"
multiple
filterable
clearable
placeholder="请选择素材组"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
<script>
import axios from 'axios'
export default {
name: 'MaterialGroupSelector',
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
options: [],
selectedValues: []
}
},
watch: {
value: {
handler(newVal) {
this.selectedValues = newVal
},
immediate: true
}
},
created() {
this.fetchOptions()
},
methods: {
async fetchOptions() {
try {
const response = await axios.get(process.env.PUTIN_API + '/material-groups')
this.options = response.data
} catch (error) {
console.error('Failed to fetch material groups:', error)
}
},
handleChange(values) {
this.$emit('input', values)
this.$emit('change', values)
}
}
}
</script>
<template>
<el-select
v-model="selectedValues"
multiple
filterable
clearable
placeholder="请选择标题组"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
<script>
import axios from 'axios'
export default {
name: 'TitleGroupSelector',
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
options: [],
selectedValues: []
}
},
watch: {
value: {
handler(newVal) {
this.selectedValues = newVal
},
immediate: true
}
},
created() {
this.fetchOptions()
},
methods: {
async fetchOptions() {
try {
const response = await axios.get(process.env.PUTIN_API + '/title-groups')
this.options = response.data
} catch (error) {
console.error('Failed to fetch title groups:', error)
}
},
handleChange(values) {
this.$emit('input', values)
this.$emit('change', values)
}
}
}
</script>
...@@ -69,6 +69,13 @@ export const constantRouterMap = [ ...@@ -69,6 +69,13 @@ export const constantRouterMap = [
component: () => import("@/views/createDelivery"), component: () => import("@/views/createDelivery"),
meta: { title: "创意投放", icon: "chart" } meta: { title: "创意投放", icon: "chart" }
}, },
{
path: '/intelligentDelivery/campaign-template',
name: "intelligentDelivery.campaign-template",
component: () => import('@/views/campaignTemplate/CampaignTemplateManage'),
meta: { title: '计划模板管理' }
}
] ]
}, },
{ {
...@@ -102,7 +109,8 @@ export const constantRouterMap = [ ...@@ -102,7 +109,8 @@ export const constantRouterMap = [
name: "assetManagement.YoutubeVideoManage", name: "assetManagement.YoutubeVideoManage",
component: () => import("@/views/uploadYoutube/YoutubeVideoManage"), component: () => import("@/views/uploadYoutube/YoutubeVideoManage"),
meta: { title: "Youtube视频管理", icon: "chart" } meta: { title: "Youtube视频管理", icon: "chart" }
} },
] ]
}, },
......
<template>
<div class="campaign-template-container">
<!-- Filter section -->
<div class="filter-section">
<el-form :inline="true" :model="condition" class="filter-form">
<el-form-item label="模板名称">
<el-input
v-model="condition.name"
placeholder="请输入模板名称"
class="filter-input"
@change="fetchData">
</el-input>
</el-form-item>
<el-form-item label="应用组">
<app-group-selector
v-model="condition.appGroups"
@change="handleAppGroupChange"
/>
</el-form-item>
<el-form-item label="地域组">
<location-group-selector
v-model="condition.locationGroups"
@change="handleLocationGroupChange"
/>
</el-form-item>
<el-form-item label="素材组">
<material-group-selector
v-model="condition.materialGroups"
@change="handleMaterialGroupChange"
/>
</el-form-item>
<el-form-item label="标题组">
<title-group-selector
v-model="condition.titleGroups"
@change="handleTitleGroupChange"
/>
</el-form-item>
<el-form-item label="描述组">
<description-group-selector
v-model="condition.descriptionGroups"
@change="handleDescriptionGroupChange"
/>
</el-form-item>
</el-form>
</div>
<el-divider></el-divider>
<!-- Table Header actions -->
<div class="header-actions">
<el-button type="primary" @click="showAddDialog">新增模板</el-button>
<div class="header-right">
<el-button icon="el-icon-refresh" @click="fetchData">刷新</el-button>
</div>
</div>
<!-- Main table -->
<el-table
:data="tableData"
border
style="width: 100%"
v-loading="loading">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="模板名称" width="150"></el-table-column>
<el-table-column prop="daily_budget" label="日预算" width="120">
<template slot-scope="scope">
{{ scope.row.daily_budget }}
</template>
</el-table-column>
<el-table-column prop="target_roas" label="目标考核ROAS" width="150">
<template slot-scope="scope">
{{ scope.row.target_roas }}
</template>
</el-table-column>
<el-table-column label="应用组" width="200">
<template slot-scope="scope">
<el-tag
v-for="group in JSON.parse(scope.row.app_groups || '[]')"
:key="group"
size="small"
style="margin: 2px">
{{ getAppGroupName(group) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="地域组" width="200">
<template slot-scope="scope">
<el-tag
v-for="group in JSON.parse(scope.row.location_groups || '[]')"
:key="group"
size="small"
style="margin: 2px">
{{ getLocationGroupName(group) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="素材组" width="200">
<template slot-scope="scope">
<el-tag
v-for="group in JSON.parse(scope.row.material_groups || '[]')"
:key="group"
size="small"
style="margin: 2px">
{{ getMaterialGroupName(group) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="标题组" width="200">
<template slot-scope="scope">
<el-tag
v-for="group in JSON.parse(scope.row.title_groups || '[]')"
:key="group"
size="small"
style="margin: 2px">
{{ getTitleGroupName(group) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="描述组" width="200">
<template slot-scope="scope">
<el-tag
v-for="group in JSON.parse(scope.row.description_groups || '[]')"
:key="group"
size="small"
style="margin: 2px">
{{ getDescriptionGroupName(group) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- Pagination -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- Add/Edit Dialog -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="50%">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="form.name"></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"></el-option>
<el-option label="应用内操作次数" :value="3"></el-option>
<el-option label="biddingStrategyGoalType" :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>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
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 { getCampaignTemplateList, createCampaignTemplate, updateCampaignTemplate, deleteCampaignTemplate } from '@/api/campaignTemplate'
export default {
name: 'CampaignTemplateManage',
components: {
AppGroupSelector,
LocationGroupSelector,
MaterialGroupSelector,
TitleGroupSelector,
DescriptionGroupSelector
},
data() {
return {
condition: {
name: '',
appGroups: [],
locationGroups: [],
materialGroups: [],
titleGroups: [],
descriptionGroups: []
},
loading: false,
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
dialogVisible: false,
dialogTitle: '',
form: {
name: '',
campaign_type: 1,
appStore: 3,
daily_budget: 0,
bidding_type: 3,
target_roas: 0,
app_groups: [],
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
},
rules: {
name: [
{ 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' }
]
},
groupNameMaps: {
app: new Map(),
location: new Map(),
material: new Map(),
title: new Map(),
description: new Map()
}
}
},
created() {
this.fetchData()
this.fetchGroupNames()
},
methods: {
async fetchData() {
this.loading = true
try {
const response = await getCampaignTemplateList()
if (response.status === 200) {
this.tableData = response.result.data
this.total = response.result.total
} else {
this.$message.error(response.msg || '获取数据失败')
}
} catch (error) {
console.error('获取数据失败:', error)
this.$message.error('获取数据失败')
}
this.loading = false
},
async fetchGroupNames() {
try {
const [app, location, material, title, description] = await Promise.all([
axios.get(process.env.PUTIN_API + '/app-groups'),
axios.get(process.env.PUTIN_API + '/location-groups'),
axios.get(process.env.PUTIN_API + '/material-groups'),
axios.get(process.env.PUTIN_API + '/title-groups'),
axios.get(process.env.PUTIN_API + '/description-groups')
])
app.data.forEach(item => this.groupNameMaps.app.set(item.id, item.name))
location.data.forEach(item => this.groupNameMaps.location.set(item.id, item.name))
material.data.forEach(item => this.groupNameMaps.material.set(item.id, item.name))
title.data.forEach(item => this.groupNameMaps.title.set(item.id, item.name))
description.data.forEach(item => this.groupNameMaps.description.set(item.id, item.name))
} catch (error) {
console.error('获取组名称失败:', error)
}
},
getAppGroupName(id) {
return this.groupNameMaps.app.get(id) || id
},
getLocationGroupName(id) {
return this.groupNameMaps.location.get(id) || id
},
getMaterialGroupName(id) {
return this.groupNameMaps.material.get(id) || id
},
getTitleGroupName(id) {
return this.groupNameMaps.title.get(id) || id
},
getDescriptionGroupName(id) {
return this.groupNameMaps.description.get(id) || id
},
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
this.fetchData()
},
handleCurrentChange(val) {
this.currentPage = val
this.fetchData()
},
showAddDialog() {
this.dialogTitle = '新增模板'
this.form = {
name: '',
campaign_type: 1,
appStore: 3,
daily_budget: 0,
bidding_type: 3,
target_roas: 0,
app_groups: [],
location_groups: [],
material_groups: [],
title_groups: [],
description_groups: []
}
this.dialogVisible = true
},
handleEdit(row) {
this.dialogTitle = '编辑模板'
this.form = {
...row,
app_groups: JSON.parse(row.app_groups || '[]'),
location_groups: JSON.parse(row.location_groups || '[]'),
material_groups: JSON.parse(row.material_groups || '[]'),
title_groups: JSON.parse(row.title_groups || '[]'),
description_groups: JSON.parse(row.description_groups || '[]')
}
this.dialogVisible = true
},
async handleDelete(row) {
try {
await this.$confirm('确认删除该模板吗?', '提示', {
type: 'warning'
})
const response = await deleteCampaignTemplate(row.id)
if (response.status === 200) {
this.$message.success('删除成功')
this.fetchData()
} else {
this.$message.error(response.msg || '删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
this.$message.error('删除失败')
}
}
},
async submitForm() {
this.$refs.form.validate(async (valid) => {
if (valid) {
const formData = {
...this.form,
app_groups: JSON.stringify(this.form.app_groups),
location_groups: JSON.stringify(this.form.location_groups),
material_groups: JSON.stringify(this.form.material_groups),
title_groups: JSON.stringify(this.form.title_groups),
description_groups: JSON.stringify(this.form.description_groups)
}
try {
let response
if (formData.id) {
response = await updateCampaignTemplate(formData)
} else {
response = await createCampaignTemplate(formData)
}
if (response.status === 200) {
this.$message.success(formData.id ? '更新成功' : '创建成功')
this.dialogVisible = false
this.fetchData()
} else {
this.$message.error(response.msg || (formData.id ? '更新失败' : '创建失败'))
}
} catch (error) {
console.error(formData.id ? '更新失败:' : '创建失败:', error)
this.$message.error(formData.id ? '更新失败' : '创建失败')
}
}
})
},
handleAppGroupChange() {
this.fetchData()
},
handleLocationGroupChange() {
this.fetchData()
},
handleMaterialGroupChange() {
this.fetchData()
},
handleTitleGroupChange() {
this.fetchData()
},
handleDescriptionGroupChange() {
this.fetchData()
}
}
}
</script>
<style scoped>
.campaign-template-container {
padding: 20px;
}
.filter-section {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.filter-form {
display: flex;
flex-wrap: wrap;
}
.filter-input {
width: 200px;
}
.header-actions {
margin: 20px 0;
display: flex;
justify-content: space-between;
}
.header-right {
display: flex;
gap: 10px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
}
.el-tag {
margin-right: 5px;
margin-bottom: 5px;
}
</style>
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
<el-submenu index="2"> <el-submenu index="2">
<template slot="title">推广管理</template> <template slot="title">推广管理</template>
<el-menu-item index="/intelligentDelivery/createDelivery">创建计划</el-menu-item> <el-menu-item index="/intelligentDelivery/createDelivery">创建计划</el-menu-item>
<el-menu-item index="/intelligentDelivery/campaign-template">计划模板</el-menu-item>
</el-submenu> </el-submenu>
<el-submenu index="3"> <el-submenu index="3">
......
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