|
|
@@ -0,0 +1,525 @@
|
|
|
+<template>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <!-- 主卡片 -->
|
|
|
+ <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
|
+ <div class="border-b border-gray-200 pb-4 mb-6">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <h2 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
|
|
|
+ <el-icon><Setting /></el-icon>
|
|
|
+ 广告计划配置管理
|
|
|
+ </h2>
|
|
|
+ <el-button type="primary" @click="handleCreate">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 创建配置
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置列表 -->
|
|
|
+ <el-table
|
|
|
+ :data="confList"
|
|
|
+ style="width: 100%"
|
|
|
+ v-loading="loading"
|
|
|
+ stripe
|
|
|
+ border
|
|
|
+ >
|
|
|
+ <el-table-column prop="id" label="ID" width="80" />
|
|
|
+ <el-table-column prop="name" label="计划名称" width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="广告类型" width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getAdTypeColor(row.adType)">
|
|
|
+ {{ getAdTypeText(row.adType) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="出价金额" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ ¥{{ (row.bidAmount / 100).toFixed(2) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="accountGroupId" label="账号组ID" width="100" />
|
|
|
+ <el-table-column prop="conversionId" label="转化ID" width="120" />
|
|
|
+ <el-table-column prop="dramaId" label="短剧ID" width="100" />
|
|
|
+ <el-table-column prop="wxCorpid" label="企微主体ID" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column label="创建时间" width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatDate(row.createTime) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button type="primary" size="small" link @click="handleEdit(row)">
|
|
|
+ <el-icon><Edit /></el-icon>
|
|
|
+ 编辑
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" size="small" link @click="handleDelete(row)">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="mt-6 flex justify-end">
|
|
|
+ <el-pagination
|
|
|
+ v-if="total > 0"
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创建/编辑配置对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="dialogTitle"
|
|
|
+ width="60%"
|
|
|
+ @close="handleDialogClose"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ label-width="120px"
|
|
|
+ >
|
|
|
+ <el-form-item label="计划名称" prop="name">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.name"
|
|
|
+ placeholder="请输入计划名称"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="广告类型" prop="adType">
|
|
|
+ <el-radio-group v-model="formData.adType">
|
|
|
+ <el-radio label="企微" :value="1" />
|
|
|
+ <el-radio label="ROI" :value="2" />
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="账号组ID" prop="accountGroupId">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.accountGroupId"
|
|
|
+ style="width: 100%"
|
|
|
+ placeholder="请选择账号组"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="accountGroup in accountGroupList"
|
|
|
+ :key="accountGroup.id"
|
|
|
+ :label="accountGroup.groupName"
|
|
|
+ :value="accountGroup.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="出价金额(元)" prop="bidAmount">
|
|
|
+ <el-input-number
|
|
|
+ v-model="bidAmountYuan"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ placeholder="请输入出价金额"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ <span class="ml-2 text-sm text-gray-500">= {{ formData.bidAmount }} 分</span>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="转化ID" prop="conversionId">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.conversionId"
|
|
|
+ :min="1"
|
|
|
+ placeholder="请输入转化ID"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="conversion in conversions"
|
|
|
+ :key="conversion.conversionId"
|
|
|
+ :label="conversion.conversionName"
|
|
|
+ :value="conversion.conversionId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="定向IDs" prop="targets">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.targets"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ placeholder="多个定向ID用逗号分隔,例如:1,2,3"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="版位IDs" prop="sites">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.sites"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ placeholder="多个版位ID用逗号分隔,例如:1,2,3"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="企微主体ID" prop="wxCorpid" v-if="formData.adType === 1">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.wxCorpid"
|
|
|
+ placeholder="请输入企微主体ID"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="短剧ID" prop="dramaId" v-if="formData.adType === 2">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formData.dramaId"
|
|
|
+ :min="1"
|
|
|
+ placeholder="请输入短剧ID"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="图片IDs" prop="imageIds">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.imageIds"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ placeholder="多个图片ID用逗号分隔,例如:1,2,3"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="落地页IDs" prop="pageIds">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.pageIds"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ placeholder="多个落地页ID用逗号分隔,例如:1,2,3"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <el-button @click="handleReCreate">预览</el-button>
|
|
|
+ <el-table :data="plans"
|
|
|
+ :preserve-expanded-content="true"
|
|
|
+ >
|
|
|
+ <el-table-column type="expand">
|
|
|
+ <template #default="props">
|
|
|
+ <div m="4">
|
|
|
+ <h3>创意</h3>
|
|
|
+ <el-table :data="props.row.adCreatives" border>
|
|
|
+ <el-table-column label="图片" prop="imageId" />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="adgroupName" label="计划名称" width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="accountId" label="账号ID" width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="target" label="定向IDs" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="site" label="版位IDs" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="adType" label="广告类型" width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getAdTypeColor(row.adType)">
|
|
|
+ {{ getAdTypeText(row.adType) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="bidAmount" label="出价金额" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ ¥{{ (row.bidAmount / 100).toFixed(2) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="conversionId" label="转化ID" width="120" />
|
|
|
+ <el-table-column prop="wxCorpid" label="企微主体ID" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="dramaId" label="短剧ID" width="100" />
|
|
|
+
|
|
|
+ <el-table-column prop="imageId" label="图片IDs" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="pageId" label="落地页IDs" width="150" show-overflow-tooltip />
|
|
|
+ </el-table>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit" :loading="submitting">
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, onMounted, watch } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import {
|
|
|
+ getAdPlanConfList,
|
|
|
+ createAdPlanConf,
|
|
|
+ updateAdPlanConf,
|
|
|
+ deleteAdPlanConf,
|
|
|
+ reCreateAdPlanConf
|
|
|
+} from '@/api/adPlanConf'
|
|
|
+import { getConversions } from '@/api/conversion'
|
|
|
+import { getAccountGroupListAll } from '@/api/accountGroup'
|
|
|
+
|
|
|
+import {
|
|
|
+ Setting,
|
|
|
+ Plus,
|
|
|
+ Edit,
|
|
|
+ Delete
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+
|
|
|
+// 数据
|
|
|
+const loading = ref(false)
|
|
|
+const submitting = ref(false)
|
|
|
+const confList = ref([])
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(0)
|
|
|
+
|
|
|
+// 对话框
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const dialogTitle = ref('创建配置')
|
|
|
+const formRef = ref(null)
|
|
|
+const editingId = ref(null)
|
|
|
+const conversions = ref([])
|
|
|
+const accountGroupList = ref([])
|
|
|
+const plans = ref([])
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const formData = ref({
|
|
|
+ name: '',
|
|
|
+ accountGroupId: null,
|
|
|
+ adType: 1,
|
|
|
+ bidAmount: 0,
|
|
|
+ conversionId: null,
|
|
|
+ wxCorpid: '',
|
|
|
+ dramaId: null,
|
|
|
+ imageIds: '',
|
|
|
+ pageIds: '',
|
|
|
+ jobNumber: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 出价金额(元)
|
|
|
+const bidAmountYuan = computed({
|
|
|
+ get: () => formData.value.bidAmount / 100,
|
|
|
+ set: (val) => {
|
|
|
+ formData.value.bidAmount = Math.round(val * 100)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const formRules = {
|
|
|
+ name: [
|
|
|
+ { required: true, message: '请输入计划名称', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ accountGroupId: [
|
|
|
+ { required: true, message: '请输入账号组ID', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ adType: [
|
|
|
+ { required: true, message: '请选择广告类型', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ bidAmount: [
|
|
|
+ { required: true, message: '请输入出价金额', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ conversionId: [
|
|
|
+ { required: true, message: '请输入转化ID', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ wxCorpid: [
|
|
|
+ { required: true, message: '请输入企微主体ID', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ dramaId: [
|
|
|
+ { required: true, message: '请输入短剧ID', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 方法
|
|
|
+const loadConfList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getAdPlanConfList(currentPage.value, pageSize.value)
|
|
|
+ if (res.success) {
|
|
|
+ confList.value = res.data?.records || []
|
|
|
+ total.value = res.data?.total || 0
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '加载配置列表失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载配置列表失败:', error)
|
|
|
+ ElMessage.error('加载配置列表失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getAdTypeText = (type) => {
|
|
|
+ const typeMap = {
|
|
|
+ 1: '企微',
|
|
|
+ 2: 'ROI'
|
|
|
+ }
|
|
|
+ return typeMap[type] || '未知'
|
|
|
+}
|
|
|
+
|
|
|
+const getAdTypeColor = (type) => {
|
|
|
+ const colorMap = {
|
|
|
+ 1: 'success',
|
|
|
+ 2: 'warning'
|
|
|
+ }
|
|
|
+ return colorMap[type] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+const formatDate = (dateStr) => {
|
|
|
+ if (!dateStr) return '-'
|
|
|
+ return new Date(dateStr).toLocaleString('zh-CN')
|
|
|
+}
|
|
|
+
|
|
|
+const handleAdTypeChange = () => {
|
|
|
+ // 切换广告类型时清空相关字段
|
|
|
+ if (formData.value.adType === 1) {
|
|
|
+ formData.value.dramaId = null
|
|
|
+ } else if (formData.value.adType === 2) {
|
|
|
+ formData.value.wxCorpid = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleCreate = () => {
|
|
|
+ dialogTitle.value = '创建配置'
|
|
|
+ editingId.value = null
|
|
|
+ formData.value = {
|
|
|
+ name: '',
|
|
|
+ accountGroupId: null,
|
|
|
+ adType: 1,
|
|
|
+ bidAmount: 1500,
|
|
|
+ conversionId: null,
|
|
|
+ targets: null,
|
|
|
+ sites: null,
|
|
|
+ wxCorpid: '',
|
|
|
+ dramaId: null,
|
|
|
+ imageIds: '',
|
|
|
+ pageIds: '',
|
|
|
+ jobNumber: ''
|
|
|
+ }
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const handleEdit = (row) => {
|
|
|
+ dialogTitle.value = '编辑配置'
|
|
|
+ editingId.value = row.id
|
|
|
+ formData.value = {
|
|
|
+ name: row.name,
|
|
|
+ accountGroupId: row.accountGroupId,
|
|
|
+ adType: row.adType,
|
|
|
+ bidAmount: row.bidAmount,
|
|
|
+ conversionId: row.conversionId,
|
|
|
+ targets: row.targets,
|
|
|
+ sites: row.sites,
|
|
|
+ wxCorpid: row.wxCorpid || '',
|
|
|
+ dramaId: row.dramaId,
|
|
|
+ imageIds: row.imageIds || '',
|
|
|
+ pageIds: row.pageIds || '',
|
|
|
+ jobNumber: row.jobNumber || ''
|
|
|
+ }
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const handleDelete = async (row) => {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm(
|
|
|
+ `确定要删除配置"${row.name}"吗?`,
|
|
|
+ '提示',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ const res = await deleteAdPlanConf(row.id)
|
|
|
+ if (res.success) {
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ await loadConfList()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '删除失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除配置失败:', error)
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleReCreate = async () => {
|
|
|
+ const res = await reCreateAdPlanConf(formData.value)
|
|
|
+ if (res.success) {
|
|
|
+ ElMessage.success('预览成功')
|
|
|
+ console.log(res.data)
|
|
|
+ plans.value = res.data
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '预览失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ try {
|
|
|
+ await formRef.value.validate()
|
|
|
+
|
|
|
+ submitting.value = true
|
|
|
+ const data = { ...formData.value }
|
|
|
+
|
|
|
+ let res
|
|
|
+ if (editingId.value) {
|
|
|
+ // 编辑
|
|
|
+ data.id = editingId.value
|
|
|
+ res = await updateAdPlanConf(data)
|
|
|
+ } else {
|
|
|
+ // 创建
|
|
|
+ res = await createAdPlanConf(data)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.success) {
|
|
|
+ ElMessage.success(editingId.value ? '更新成功' : '创建成功')
|
|
|
+ dialogVisible.value = false
|
|
|
+ await loadConfList()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== false) {
|
|
|
+ console.error('提交失败:', error)
|
|
|
+ ElMessage.error('操作失败')
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleDialogClose = () => {
|
|
|
+ formRef.value?.resetFields()
|
|
|
+}
|
|
|
+
|
|
|
+const loadConversions = async () => {
|
|
|
+ const res = await getConversions()
|
|
|
+ if (res.success) {
|
|
|
+ conversions.value = res.data
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '加载转化列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadAccountGroupList = async () => {
|
|
|
+ const res = await getAccountGroupListAll()
|
|
|
+ if (res.success) {
|
|
|
+ accountGroupList.value = res.data
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '加载账号组列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听分页变化
|
|
|
+watch([currentPage, pageSize], () => {
|
|
|
+ loadConfList()
|
|
|
+})
|
|
|
+
|
|
|
+// 生命周期
|
|
|
+onMounted(() => {
|
|
|
+ loadConfList()
|
|
|
+ loadConversions()
|
|
|
+ loadAccountGroupList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 自定义样式 */
|
|
|
+</style>
|