|
|
@@ -4,14 +4,19 @@
|
|
|
<div class="border-b border-gray-200 pb-4 mb-6">
|
|
|
<div class="flex items-center ">
|
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
|
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24"
|
|
|
+ stroke="currentColor">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
|
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
|
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
|
</svg>
|
|
|
{{ isEdit ? '编辑配置' : '新增配置' }}
|
|
|
</h2>
|
|
|
<el-button @click="handleBack">
|
|
|
- <el-icon><ArrowLeft /></el-icon>
|
|
|
+ <el-icon>
|
|
|
+ <ArrowLeft />
|
|
|
+ </el-icon>
|
|
|
返回列表
|
|
|
</el-button>
|
|
|
</div>
|
|
|
@@ -22,73 +27,95 @@
|
|
|
<el-input v-model="form.ruleName" placeholder="请输入规则名称" style="width: 50%" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="广告组" prop="adGroupId">
|
|
|
- <el-select v-model="form.adGroupId" placeholder="请选择广告组" style="width: 50%" filterable>
|
|
|
+ <el-select v-model="form.adGroupId" placeholder="请选择广告组" style="width: 50%" filterable>
|
|
|
<el-option label="全部广告组" :value="0" />
|
|
|
- <el-option
|
|
|
- v-for="group in accountGroupList"
|
|
|
- :key="group.id"
|
|
|
- :label="`${group.groupName} (${group.id})`"
|
|
|
- :value="group.id"
|
|
|
- />
|
|
|
+ <el-option v-for="group in accountGroupList" :key="group.id" :label="`${group.groupName} (${group.id})`"
|
|
|
+ :value="group.id" />
|
|
|
</el-select>
|
|
|
<p class="text-xs text-gray-500 mt-1">选择"全部广告组"表示不考虑分组情况</p>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="新广告天数" prop="newAdDays">
|
|
|
<el-input-number v-model="form.newAdDays" :min="1" placeholder="请输入新广告天数定义" style="width: 50%" />
|
|
|
</el-form-item>
|
|
|
-
|
|
|
+ <p class="text-xs text-red-500" style="padding-left :120px">回车录入预算登记</p>
|
|
|
+ <el-form-item label="日预算等级(元)" prop="dailyBudgetLevelArr">
|
|
|
+ <el-input-tag
|
|
|
+ v-model="form.dailyBudgetLevelArr"
|
|
|
+ placeholder="请输入(元)"
|
|
|
+ aria-label=""
|
|
|
+ style="width: 50%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="日预算升级占比" prop="dailyBudgetRateTop">
|
|
|
+ <el-input-number v-model="form.dailyBudgetRateTop" :precision="2" :min="0.1" :step="0.1" :max="1" placeholder="请输入日预算升级百分比" style="width: 50%" />
|
|
|
+ </el-form-item>
|
|
|
<!-- 规则配置 -->
|
|
|
<el-divider content-position="left">规则配置</el-divider>
|
|
|
<p class="text-sm text-red-500">规则执行将按照最小成本从高到低排序,当最小成本为0时候,将不考虑成本,直接计算ROI或粉价</p>
|
|
|
- <div class="rules-container">
|
|
|
+ <div class="rules-container">
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
|
<div v-for="(rule, index) in form.rules" :key="index" class="rule-item p-3 bg-gray-50 rounded-lg">
|
|
|
<div class="flex items-center justify-between mb-1">
|
|
|
<span class="font-medium text-gray-700">【规则 {{ index + 1 }}】</span>
|
|
|
<div class="bg-blue-50 rounded text-xs text-blue-800">
|
|
|
- <template v-if="rule.type === 1">
|
|
|
- <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 > ${rule.minCost}元 且 ` : '' }} ROI < {{ rule.minIor }} 时暂停
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 > ${rule.minCost}元 且 ` : '' }}粉价 > {{ rule.scanMaxCost }}元 时暂停
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- <el-button type="danger" size="small" link @click="removeRule(index)">
|
|
|
- <el-icon><Delete /></el-icon>
|
|
|
+ <template v-if="rule.type === 1">
|
|
|
+ <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 >
|
|
|
+ ${rule.minCost}元 且 ` : '' }} ROI < {{ rule.minIor }} 时暂停
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 >
|
|
|
+ ${rule.minCost}元 且 ` : '' }}粉价 > {{ rule.scanMaxCost }}元 时暂停
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <el-button type="danger" size="small" link @click="removeRule(index)">
|
|
|
+ <el-icon>
|
|
|
+ <Delete />
|
|
|
+ </el-icon>
|
|
|
删除
|
|
|
</el-button>
|
|
|
</div>
|
|
|
-
|
|
|
+
|
|
|
<!-- 广告类型 radio -->
|
|
|
- <el-form-item style="margin-bottom:4px" label="广告" :prop="`rules.${index}.newAd`" :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
|
|
|
+ <el-form-item style="margin-bottom:4px" label="广告" :prop="`rules.${index}.newAd`"
|
|
|
+ :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
|
|
|
<el-radio-group v-model="rule.newAd" size="small">
|
|
|
<el-radio :label="1">新广告</el-radio>
|
|
|
<el-radio :label="2">老广告</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<!-- 规则类型 radio -->
|
|
|
- <el-form-item style="margin-bottom:4px" label="规则" :prop="`rules.${index}.type`" :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
|
|
|
+ <el-form-item style="margin-bottom:4px" label="规则" :prop="`rules.${index}.type`"
|
|
|
+ :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
|
|
|
<el-radio-group v-model="rule.type" size="small">
|
|
|
<el-radio :label="1">ROI规则</el-radio>
|
|
|
<el-radio :label="2">粉价规则</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<!-- 最小成本 -->
|
|
|
- <el-form-item style="margin-bottom:4px" label="最小成本" :prop="`rules.${index}.minCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
|
|
|
+ <el-form-item style="margin-bottom:4px" label="最小成本" :prop="`rules.${index}.minCost`"
|
|
|
+ :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
|
|
|
<el-input-number v-model="rule.minCost" :min="0" placeholder="最小成本" style="width: 80%" size="small" />
|
|
|
</el-form-item>
|
|
|
<!-- ROI规则显示最小ROI -->
|
|
|
- <el-form-item v-if="rule.type === 1" style="margin-bottom:4px" label="最小ROI" :prop="`rules.${index}.minIor`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
|
|
|
- <el-input-number v-model="rule.minIor" :min="0" placeholder="最小ROI" style="width: 80%" size="small" />
|
|
|
+ <el-form-item v-if="rule.type === 1" style="margin-bottom:4px" label="最小ROI"
|
|
|
+ :prop="`rules.${index}.minIor`" :rules="{ required: true, message: '请输入', trigger: 'blur' }"
|
|
|
+ label-width="80px">
|
|
|
+ <el-input-number v-model="rule.minIor" :precision="2" :min="0" placeholder="最小ROI" style="width: 80%" size="small" />
|
|
|
</el-form-item>
|
|
|
<!-- 粉价规则显示最大粉价 -->
|
|
|
- <el-form-item v-if="rule.type === 2" style="margin-bottom:4px" label="最大粉价" :prop="`rules.${index}.scanMaxCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
|
|
|
- <el-input-number v-model="rule.scanMaxCost" :min="0" placeholder="最大粉价" style="width: 80%" size="small" />
|
|
|
+ <el-form-item v-if="rule.type === 2" style="margin-bottom:4px" label="最大粉价"
|
|
|
+ :prop="`rules.${index}.scanMaxCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }"
|
|
|
+ label-width="80px">
|
|
|
+ <el-input-number v-model="rule.scanMaxCost" :precision="2" :min="0" placeholder="最大粉价" style="width: 80%"
|
|
|
+ size="small" />
|
|
|
</el-form-item>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-button type="primary" link @click="addRule" class="w-full">
|
|
|
- <el-icon><Plus /></el-icon>
|
|
|
+ <el-icon>
|
|
|
+ <Plus />
|
|
|
+ </el-icon>
|
|
|
添加规则
|
|
|
</el-button>
|
|
|
</div>
|
|
|
@@ -103,6 +130,10 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
+defineOptions({
|
|
|
+ name: 'AdAutoConfEdit'
|
|
|
+})
|
|
|
+
|
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
@@ -114,6 +145,11 @@ import {
|
|
|
} from '@/api/adAutoConf'
|
|
|
import { getAccountGroupListAll } from '@/api/accountGroup'
|
|
|
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'AdAutoConfEdit'
|
|
|
+})
|
|
|
+
|
|
|
// 响应式数据
|
|
|
const submitting = ref(false)
|
|
|
const formRef = ref(null)
|
|
|
@@ -132,6 +168,11 @@ const form = reactive({
|
|
|
ruleName: '',
|
|
|
adGroupId: 0,
|
|
|
newAdDays: 3,
|
|
|
+ // 日预算等级
|
|
|
+ dailyBudgetLevelArr: [100, 300, 500, 1000, 5000, 10000],
|
|
|
+ dailyBudgetLevel: '[10000, 30000, 50000, 100000, 500000, 1000000]',
|
|
|
+ // 达到日预算百分比后升级预算
|
|
|
+ dailyBudgetRateTop: 0.9,
|
|
|
rules: []
|
|
|
})
|
|
|
|
|
|
@@ -140,6 +181,9 @@ const rules = {
|
|
|
ruleName: [
|
|
|
{ required: true, message: '请输入规则名称', trigger: 'blur' }
|
|
|
],
|
|
|
+ dailyBudgetLevelArr: [
|
|
|
+ { required: true, message: '请选择日预算等级', trigger: 'change' }
|
|
|
+ ],
|
|
|
adGroupId: [
|
|
|
{ required: true, message: '请选择广告组', trigger: 'change' }
|
|
|
],
|
|
|
@@ -159,9 +203,9 @@ const createDefaultRule = () => ({
|
|
|
|
|
|
const createFirstDefaultRule = () => ([
|
|
|
{ type: 1, newAd: 1, minCost: 100, minIor: 0.15, scanMaxCost: 10 },
|
|
|
- { type: 1, newAd: 1, minCost: 30, minIor: 0.10, scanMaxCost: 10 },
|
|
|
- { type: 2, newAd: 1, minCost: 0, minIor: 0, scanMaxCost: 10 },
|
|
|
- { type: 2, newAd: 2, minCost: 0, minIor: 0, scanMaxCost: 100 }
|
|
|
+ { type: 1, newAd: 1, minCost: 30, minIor: 0.10, scanMaxCost: 10 },
|
|
|
+ { type: 2, newAd: 1, minCost: 0, minIor: 0, scanMaxCost: 10 },
|
|
|
+ { type: 2, newAd: 2, minCost: 0, minIor: 0, scanMaxCost: 100 }
|
|
|
])
|
|
|
|
|
|
// 添加规则
|
|
|
@@ -202,6 +246,9 @@ const loadDetail = async (id) => {
|
|
|
form.jobNumber = data.jobNumber
|
|
|
form.adGroupId = data.adGroupId
|
|
|
form.newAdDays = data.newAdDays
|
|
|
+ form.dailyBudgetLevelArr = data.dailyBudgetLevel ? JSON.parse(data.dailyBudgetLevel).map(r => r / 100) : [100, 300, 500, 1000, 5000, 10000]
|
|
|
+ form.dailyBudgetLevel = data.dailyBudgetLevel
|
|
|
+ form.dailyBudgetRateTop = data.dailyBudgetRateTop
|
|
|
// 解析 rules JSON
|
|
|
if (data.rules) {
|
|
|
try {
|
|
|
@@ -246,7 +293,9 @@ const handleSubmit = async () => {
|
|
|
jobNumber: form.jobNumber,
|
|
|
adGroupId: form.adGroupId,
|
|
|
newAdDays: form.newAdDays,
|
|
|
- rules: JSON.stringify(form.rules)
|
|
|
+ rules: JSON.stringify(form.rules),
|
|
|
+ dailyBudgetLevel: JSON.stringify(form.dailyBudgetLevelArr.map(r => r * 100).sort((a, b) => a - b)),
|
|
|
+ dailyBudgetRateTop: form.dailyBudgetRateTop
|
|
|
}
|
|
|
let response
|
|
|
if (isEdit.value) {
|
|
|
@@ -254,7 +303,7 @@ const handleSubmit = async () => {
|
|
|
} else {
|
|
|
response = await saveAdAutoConf(data)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (response.success) {
|
|
|
ElMessage.success(isEdit.value ? '更新成功' : '新增成功')
|
|
|
handleBack()
|
|
|
@@ -273,7 +322,7 @@ const handleSubmit = async () => {
|
|
|
// 页面加载
|
|
|
onMounted(() => {
|
|
|
loadAccountGroupList()
|
|
|
-
|
|
|
+
|
|
|
// 检查是否有id参数,有则是编辑模式
|
|
|
const id = route.query.id
|
|
|
if (id) {
|