feat(marketing): add ad placement management UI
- CRUD for ad placements (rewarded video, banner, interstitial) - Integrated into marketing management page with table and dialog Made-with: Cursor
This commit is contained in:
@@ -84,3 +84,33 @@ export function deleteMarketingTemplate(id) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getAdPlacements() {
|
||||
return request({
|
||||
url: '/api/admin/marketing/ad-placements',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function createAdPlacement(data) {
|
||||
return request({
|
||||
url: '/api/admin/marketing/ad-placements',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateAdPlacement(id, data) {
|
||||
return request({
|
||||
url: `/api/admin/marketing/ad-placements/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteAdPlacement(id) {
|
||||
return request({
|
||||
url: `/api/admin/marketing/ad-placements/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,6 +152,84 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 广告位管理 -->
|
||||
<el-card class="section-card">
|
||||
<template #header>
|
||||
<div class="header-row">
|
||||
<span>广告位管理</span>
|
||||
<el-button type="primary" @click="openAdDialog()">新增广告位</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="adsLoading" :data="adPlacements" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="广告位名称" min-width="140" />
|
||||
<el-table-column prop="ad_type" label="广告类型" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.ad_type === 'rewarded_video' ? 'warning' : 'info'">
|
||||
{{ { rewarded_video: '激励视频', banner: 'Banner', interstitial: '插屏' }[row.ad_type] || row.ad_type }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ad_unit_id" label="广告单元 ID" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.ad_unit_id || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mini_program_id" label="小程序 ID" width="100" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="备注" min-width="140" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openAdDialog(row)">编辑</el-button>
|
||||
<el-popconfirm title="确认删除该广告位?" @confirm="handleDeleteAd(row.id)">
|
||||
<template #reference>
|
||||
<el-button link type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 广告位弹窗 -->
|
||||
<el-dialog v-model="adDialogVisible" :title="adForm.id ? '编辑广告位' : '新增广告位'" width="520px">
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="广告位名称" required>
|
||||
<el-input v-model="adForm.name" placeholder="如: 保存营销图" />
|
||||
</el-form-item>
|
||||
<el-form-item label="广告类型" required>
|
||||
<el-select v-model="adForm.ad_type" style="width: 100%">
|
||||
<el-option label="激励视频" value="rewarded_video" />
|
||||
<el-option label="Banner" value="banner" />
|
||||
<el-option label="插屏" value="interstitial" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="广告单元 ID">
|
||||
<el-input v-model="adForm.ad_unit_id" placeholder="在微信后台申请后填入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序 ID" required>
|
||||
<el-input-number v-model="adForm.mini_program_id" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-switch v-model="adForm.statusBool" active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="adForm.description" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="adDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="adSaving" @click="saveAd">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="categoryDialogVisible"
|
||||
:title="categoryForm.id ? '编辑分类' : '新增分类'"
|
||||
@@ -250,7 +328,11 @@ import {
|
||||
getMarketingStats,
|
||||
getMarketingTemplates,
|
||||
updateMarketingCategory,
|
||||
updateMarketingTemplate
|
||||
updateMarketingTemplate,
|
||||
getAdPlacements,
|
||||
createAdPlacement,
|
||||
updateAdPlacement,
|
||||
deleteAdPlacement
|
||||
} from '../../api/marketing'
|
||||
|
||||
const errorMessage = ref('')
|
||||
@@ -300,6 +382,20 @@ const templateForm = reactive({
|
||||
statusBool: true
|
||||
})
|
||||
|
||||
const adPlacements = ref([])
|
||||
const adsLoading = ref(false)
|
||||
const adDialogVisible = ref(false)
|
||||
const adSaving = ref(false)
|
||||
const adForm = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
ad_type: 'rewarded_video',
|
||||
ad_unit_id: '',
|
||||
mini_program_id: 3,
|
||||
statusBool: true,
|
||||
description: ''
|
||||
})
|
||||
|
||||
const parseDownloads = (data, key) => {
|
||||
const candidates = [
|
||||
data?.[key],
|
||||
@@ -369,9 +465,77 @@ const loadStats = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadAdPlacements = async () => {
|
||||
adsLoading.value = true
|
||||
try {
|
||||
const res = await getAdPlacements()
|
||||
adPlacements.value = res.data || []
|
||||
} catch (error) {
|
||||
errorMessage.value = '加载广告位失败'
|
||||
} finally {
|
||||
adsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openAdDialog = (row) => {
|
||||
if (!row) {
|
||||
adForm.id = null
|
||||
adForm.name = ''
|
||||
adForm.ad_type = 'rewarded_video'
|
||||
adForm.ad_unit_id = ''
|
||||
adForm.mini_program_id = 3
|
||||
adForm.statusBool = true
|
||||
adForm.description = ''
|
||||
} else {
|
||||
adForm.id = row.id
|
||||
adForm.name = row.name || ''
|
||||
adForm.ad_type = row.ad_type || 'rewarded_video'
|
||||
adForm.ad_unit_id = row.ad_unit_id || ''
|
||||
adForm.mini_program_id = row.mini_program_id || 3
|
||||
adForm.statusBool = row.status === 1
|
||||
adForm.description = row.description || ''
|
||||
}
|
||||
adDialogVisible.value = true
|
||||
}
|
||||
|
||||
const saveAd = async () => {
|
||||
if (!adForm.name.trim()) {
|
||||
ElMessage.warning('请填写广告位名称')
|
||||
return
|
||||
}
|
||||
adSaving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
name: adForm.name.trim(),
|
||||
ad_type: adForm.ad_type,
|
||||
ad_unit_id: adForm.ad_unit_id.trim(),
|
||||
mini_program_id: adForm.mini_program_id,
|
||||
status: adForm.statusBool ? 1 : 0,
|
||||
description: adForm.description.trim()
|
||||
}
|
||||
if (adForm.id) {
|
||||
await updateAdPlacement(adForm.id, payload)
|
||||
ElMessage.success('广告位更新成功')
|
||||
} else {
|
||||
await createAdPlacement(payload)
|
||||
ElMessage.success('广告位创建成功')
|
||||
}
|
||||
adDialogVisible.value = false
|
||||
await loadAdPlacements()
|
||||
} finally {
|
||||
adSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAd = async (id) => {
|
||||
await deleteAdPlacement(id)
|
||||
ElMessage.success('广告位删除成功')
|
||||
await loadAdPlacements()
|
||||
}
|
||||
|
||||
const loadAll = async () => {
|
||||
errorMessage.value = ''
|
||||
await Promise.all([loadCategories(), loadTemplates(), loadTemplateCount(), loadStats()])
|
||||
await Promise.all([loadCategories(), loadTemplates(), loadTemplateCount(), loadStats(), loadAdPlacements()])
|
||||
}
|
||||
|
||||
const resetCategoryForm = () => {
|
||||
|
||||
Reference in New Issue
Block a user