瀏覽代碼

✨ feat<自动化规则>&&<报表管理>: 自动化规则相关功能添加;报表管理选择器组件修改

xinyan 10 月之前
父節點
當前提交
c4512664eb

+ 2 - 2
src/components/TimerBidTable/index.vue

@@ -206,8 +206,9 @@ const applyBid = (timeRangeValue: string, scheduleValue: string, bidValue: numbe
 
   days.forEach(day => {
     for (let hour = start; hour <= end; hour++) {
-      items.value[day][hour].value = bidValue;
       items.value[day][hour].selected = true;
+      items.value[day][hour].value = bidValue;
+      bidData.value[day][hour] = bidValue;
     }
   });
 };
@@ -234,7 +235,6 @@ watch(
         }
       }
       bidData.value = props.data;
-      console.log('watch', props.data);
     },
     {immediate: true}
 );

+ 1 - 1
src/components/select-button/index.vue

@@ -29,7 +29,7 @@ const props = withDefaults(defineProps<Props>(), {
   placement: "bottom-start",
   btnType: 'primary',
   btnColor: '#3359b5',
-  btnIcon: ''
+  btnIcon: 'CirclePlus'
 })
 const emits = defineEmits(['click'])
 

+ 1 - 1
src/views/adManage/sp/campaigns/campaignDetail/automation/index.vue

@@ -57,7 +57,7 @@ const baseData = ref({
   profileId: props.profileId,
   ruleType: activeTab,
 });
-console.log('sp',baseData.value);
+
 onMounted(async () => {
   await initData();
 });

+ 1 - 0
src/views/components/auto/auto-campaigns/common.ts

@@ -64,6 +64,7 @@ export const userFormData = (props: Props) => {
       delete body.rule['modifier_name']
       delete body.rule.id
     }
+    //console.log('body',body);
     await api.saveCampaignRule(body)
   }
 

+ 17 - 2
src/views/components/auto/auto-campaigns/neg-keyword.vue

@@ -11,6 +11,7 @@ import FreqSetting from '../freq-setting.vue';
 import conditionBuilder from '/@/components/conditionBuilder/index.vue';
 import XEUtils from 'xe-utils';
 import { userFormData } from './common';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
 
 interface Props {
   data: {
@@ -29,6 +30,7 @@ const formRef = ref();
 const searchTermAddRef = ref();
 const condiBuilderRef = ref();
 const { formData, submitFormData } = userFormData(props);
+const submitDialogVisible = ref(false);
 
 const submitForm = async () => {
   const valid2 = await searchTermAddRef.value.validate();
@@ -45,6 +47,14 @@ const submitForm = async () => {
 const cancel = () => {
   emits('refresh');
 };
+
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submitForm();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
 </script>
 
 <template>
@@ -74,10 +84,15 @@ const cancel = () => {
 
     <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
     </el-card>
-
+    <SaveRuleDialog
+        v-model="submitDialogVisible"
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submitForm"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>

+ 97 - 0
src/views/components/auto/auto-campaigns/save-rule-dialog.vue

@@ -0,0 +1,97 @@
+<script lang="ts" setup>
+import { ref, watch } from 'vue';
+
+interface Props {
+  modelValue: boolean;
+  formRef: any;
+  formData: any;
+}
+
+const props = defineProps<Props>();
+const emits = defineEmits(['update:modelValue', 'submit']);
+
+const selectedOption = ref(1);
+const form = ref({
+  templateName: ''
+});
+const rules = {
+  templateName: [
+    { required: true, message: '请输入不大于150个字符的模板名称', trigger: 'blur' },
+  ]
+};
+
+const dialogVisible = ref(props.modelValue);
+
+watch(() => props.modelValue, (newValue) => {
+  dialogVisible.value = newValue;
+  //console.log(dialogVisible.value, newValue);
+});
+
+function handleSave() {
+  props.formRef.validate((valid: boolean) => {
+    if (valid) {
+      emits('submit', {
+        selectedOption: selectedOption.value,
+        templateName: form.value.templateName,
+      });
+      dialogVisible.value = false;
+    } else {
+      console.log('验证失败');
+    }
+  });
+}
+
+function handleCancel() {
+  dialogVisible.value = false;
+}
+
+watch(dialogVisible, (newValue) => {
+  emits('update:modelValue', newValue);
+});
+</script>
+
+<template>
+  <el-dialog
+      v-model="dialogVisible"
+      align-center
+      title="保存"
+      width="30%"
+  >
+    <div>
+      <el-radio-group v-model="selectedOption">
+        <div style="display: block;">
+          <el-radio :label="1">仅应用于当前广告活动</el-radio>
+        </div>
+        <div style="display: block;">
+          <el-radio :label="2">应用于当前活动且保存为模板</el-radio>
+        </div>
+      </el-radio-group>
+    </div>
+    <div v-if="selectedOption === 2">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
+        <el-form-item class="custom-form-item" label="模板名称" prop="templateName">
+          <el-input v-model="form.templateName" maxlength="150" placeholder="请输入不大于150个字符的字符" />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleSave">保存</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped>
+.el-radio-group {
+  display: inline-flex;
+  font-size: 0;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.custom-form-item {
+  flex-direction: column;
+}
+</style>

+ 17 - 2
src/views/components/auto/auto-campaigns/search-term.vue

@@ -12,6 +12,7 @@ import FreqSetting from '../freq-setting.vue';
 import conditionBuilder from '/@/components/conditionBuilder/index.vue';
 import { userFormData } from './common';
 import XEUtils from 'xe-utils';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
 
 interface Props {
   data: {
@@ -31,6 +32,7 @@ const searchTermAddRef = ref();
 const searchTermBidRef = ref();
 const condiBuilderRef = ref();
 const { formData, submitFormData } = userFormData(props);
+const submitDialogVisible = ref(false);
 
 const submitForm = async () => {
   const valid2 = await searchTermAddRef.value.validate();
@@ -48,6 +50,14 @@ const submitForm = async () => {
 const cancel = () => {
   emits('refresh');
 };
+
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submitForm();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
 </script>
 
 <template>
@@ -65,10 +75,15 @@ const cancel = () => {
     <el-card class="mt-3">
       <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
     </el-card>
-
+    <SaveRuleDialog
+        v-model="submitDialogVisible"
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submitForm"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>

+ 19 - 2
src/views/components/auto/auto-campaigns/switch-campaign.vue

@@ -5,11 +5,12 @@
  * @Author: Cheney
  */
 
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import SelectTmpl from './select-tmpl.vue';
 import conditionBuilder from '/@/components/conditionBuilder/index.vue';
 import { userFormData } from './common';
 import FreqSetting from '../freq-setting.vue';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
 
 interface Props {
   data: {
@@ -28,6 +29,7 @@ const condiBuilderRef = ref();
 const { formData, submitFormData } = userFormData(props);
 formData.value.rule.action.state = [];
 formData.value.rule.action.setTime = '';
+const submitDialogVisible = ref(false);
 
 const condidateFields = [
   { label: '曝光量', value: 'impressions' },
@@ -65,6 +67,15 @@ async function submitForm() {
 function cancel() {
   emits('refresh');
 }
+
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submitForm();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
+
 </script>
 
 <template>
@@ -107,9 +118,15 @@ function cancel() {
       </div>
     </el-card>
 
+    <SaveRuleDialog
+        v-model="submitDialogVisible"
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submitForm"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>

+ 17 - 2
src/views/components/auto/auto-campaigns/target-rule.vue

@@ -11,6 +11,7 @@ import SelectTmpl from './select-tmpl.vue';
 import FreqSetting from '../freq-setting.vue';
 import { userFormData } from './common';
 import TargetRuleSetting from '../target-rule-setting.vue';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
 
 interface Props {
   data: {
@@ -27,6 +28,7 @@ const emits = defineEmits(['refresh']);
 const formRef = ref();
 const ruleSettingRef = ref();
 const { formData, submitFormData } = userFormData(props);
+const submitDialogVisible = ref(false);
 
 const submitForm = async () => {
   const valid2 = await ruleSettingRef.value.validateForm();
@@ -43,6 +45,14 @@ const submitForm = async () => {
 function cancel() {
   emits('refresh');
 }
+
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submitForm();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
 </script>
 
 <template>
@@ -70,10 +80,15 @@ function cancel() {
     <el-card class="mt-3">
       <TargetRuleSetting :rule="formData.rule" ref="ruleSettingRef" :disabled="formData.useTmpl" />
     </el-card>
-
+    <SaveRuleDialog
+        v-model="submitDialogVisible"
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submitForm"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>

+ 40 - 77
src/views/components/auto/auto-campaigns/timer-bid.vue

@@ -9,6 +9,7 @@ import { ref } from 'vue';
 import TimerBidTable from '/@/components/TimerBidTable/index.vue';
 import SelectTmpl from './select-tmpl.vue';
 import { userFormData } from './common';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
 
 interface Props {
   data: {
@@ -25,11 +26,13 @@ const props = defineProps<Props>();
 const emits = defineEmits(['refresh']);
 
 const formRef = ref();
-const {formData, submitFormData} = userFormData(props);
+const { formData, submitFormData } = userFormData(props);
+const tableRef = ref(null);
 
 const timeRange = ref('Option1');
 const schedule = ref('Option1');
 const bid = ref('1.0');
+const bidError = ref(''); // 错误信息
 const timeRangeOptions = [
   {
     value: 'Option1',
@@ -60,7 +63,6 @@ const timeRangeOptions = [
     label: '深夜: 21:00-23:59',
   },
 ];
-
 const scheduleOptions = [
   {
     value: 'Option1',
@@ -77,36 +79,15 @@ const scheduleOptions = [
 ];
 const isVisible = ref(true);
 
-const submitDialogVisible = ref(false)
-const selectedOption = ref(1);
-const form = ref({
-  templateName: ''
-});
-const rules = {
-  templateName: [
-    { required: true, message: '请输入不大于150个字符的模板名称', trigger: 'blur' },
-  ]
-
-};
-
-//验证是否已设置竞价
-function checkConditions(rule: any, value: any, callback: any) {
-  for (const bidList of formData.value.rule.conditions) {
-    for (const val of bidList) {
-      if (val > 0 && val < 100) {
-        return callback();
-      }
-    }
-  }
-  callback(new Error('请先设置竞价!'));
-}
+const submitDialogVisible = ref(false);
 
 async function submit() {
   formRef.value.validate(async (valid: any) => {
     if (valid) {
       await submitFormData();
+      console.log('formData', formData.value);
       emits('refresh');
-      submitDialogVisible.value = true
+      submitDialogVisible.value = true;
     } else {
       console.log('验证失败');
     }
@@ -117,14 +98,36 @@ function cancel() {
   emits('refresh');
 }
 
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submit();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
+
 function handleClose() {
   isVisible.value = false;
 }
 
-const tableRef = ref(null);
+// 验证bid输入值是否合法
+function validateBid(value: string) {
+  if (value === '') {
+    bidError.value = ''; // 空值时不显示错误信息
+  } else if (Number(value) >= 100 || Number(value) <= 0) {
+    bidError.value = '请输入数值大于0且小于100的数值,可精确到小数点后2位';
+  } else {
+    bidError.value = '';
+  }
+}
+
+function handleBidChange(value: string) {
+  validateBid(value);
+}
 
 const handleApply = () => {
-  if (tableRef.value) {
+  validateBid(bid.value);
+  if (!bidError.value && tableRef.value) {
     tableRef.value.applyBid(timeRange.value, schedule.value, parseFloat(bid.value));
   }
 };
@@ -153,7 +156,7 @@ const handleApply = () => {
     <SelectTmpl :data="formData" />
     <el-card class="mt-3">
       <el-form ref="formRef" :model="formData" label-position="top" style="margin-top: 20px">
-        <el-form-item :rules="[{ validator: checkConditions, trigger: 'blur' }]" prop="rule.conditions">
+        <el-form-item prop="rule.conditions">
           <template #label>
             <span class="custom-title-icon"></span>
             <span class="asj-h3">设置竞价</span>
@@ -169,11 +172,11 @@ const handleApply = () => {
               <el-input v-model="bid" :disabled="formData.useTmpl"
                         clearable
                         oninput="value=value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.').replace('.', '$#$').replace(/\./g, '').replace('$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').replace(/^\./g, '')"
-                        min="0"
-                        max="100"
                         placeholder="1.0"
                         style="width: 150px"
+                        @change="handleBidChange"
               ></el-input>
+              <div v-if="bidError" style="font-size: 12px;">{{ bidError }}</div>
               <el-button :disabled="formData.useTmpl" class="active-btn" link style="color: #3c58af"
                          @click="handleApply">应用
               </el-button>
@@ -188,43 +191,15 @@ const handleApply = () => {
           @click="formRef.clearValidate('rule.conditions')" />
     </el-card>
 
-    <el-dialog
+    <SaveRuleDialog
         v-model="submitDialogVisible"
-        title="保存"
-        width= 30%
-        align-center
-    >
-      <div>
-        <el-radio-group v-model="selectedOption">
-          <!-- 将每个radio按钮包裹在div中,并使用样式使其独占一行 -->
-          <div style="display: block;">
-            <el-radio :label="1">仅应用于当前广告活动</el-radio>
-          </div>
-          <div style="display: block;">
-            <el-radio :label="2">应用于当前活动且保存为模板</el-radio>
-          </div>
-        </el-radio-group>
-      </div>
-      <div v-if="selectedOption === 2">
-        <el-form  :model="form" :rules="rules" ref="formRef" label-width="auto">
-          <el-form-item label="模板名称" prop="templateName" class="custom-form-item">
-            <el-input v-model="form.templateName" placeholder="请输入不大于150个字符的字符" maxlength="150" />
-          </el-form-item>
-        </el-form>
-      </div>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="submitDialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="submit">
-            保存
-          </el-button>
-        </div>
-      </template>
-    </el-dialog>
-
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submit"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitDialogVisible = true">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>
@@ -268,16 +243,4 @@ const handleApply = () => {
   display: block;
   max-width: 100%;
 }
-
-.el-radio-group {
-  display: inline-flex;
-  font-size: 0;
-  flex-direction: column;
-  align-items: flex-start;
-}
-
-.custom-form-item {
-  flex-direction: column;
-}
-
 </style>

+ 24 - 5
src/views/components/auto/auto-campaigns/timer-budget.vue

@@ -10,6 +10,8 @@ import SelectTmpl from './select-tmpl.vue';
 import TimerBudgetTable from '/@/components/TimerBudgetTable/index.vue';
 import XEUtils from 'xe-utils';
 import { userFormData } from './common';
+import SaveRuleDialog from '/@/views/components/auto/auto-campaigns/save-rule-dialog.vue';
+import { ElMessage } from 'element-plus';
 
 interface Props {
   data: {
@@ -26,6 +28,9 @@ const emits = defineEmits(['refresh']);
 const formRef = ref();
 const { formData, submitFormData } = userFormData(props);
 
+const submitDialogVisible = ref(false);
+
+
 function checkConditions(rule: any, value: any, callback: any) {
   for (const bidList of formData.value.rule.conditions) {
     for (const info of bidList) {
@@ -48,6 +53,14 @@ async function submitForm() {
   });
 }
 
+function handleSubmit() {
+  if (formData.value.useTmpl) {
+    submitForm();
+  } else {
+    submitDialogVisible.value = true;
+  }
+}
+
 function cancel() {
   emits('refresh');
 }
@@ -59,21 +72,27 @@ function cancel() {
     <SelectTmpl :data="formData" />
     <el-card class="mt-3">
       <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef">
-        <el-form-item prop="rule.conditions" :rules="[{ validator: checkConditions, trigger: 'xxx' }]">
+        <el-form-item prop="rule.conditions" :rules="[{ validator: checkConditions, trigger: 'blur'}]">
           <template #label>
             <span class="custom-title-icon"></span>
             <span class="asj-h3">设置预算</span>
           </template>
           <TimerBudgetTable
-            :data="formData.rule.conditions"
-            @click="formRef.clearValidate('rule.conditions')"
-            :disabled="formData.useTmpl" />
+              :data="formData.rule.conditions"
+              @click="formRef.clearValidate('rule.conditions')"
+              :disabled="formData.useTmpl" />
         </el-form-item>
       </el-form>
     </el-card>
+    <SaveRuleDialog
+        v-model="submitDialogVisible"
+        :formData="formData"
+        :formRef="formRef"
+        @submit="submitForm"
+    />
     <div class="auto-page-foot">
       <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+      <el-button style="width: 200px" type="primary" @click="handleSubmit">提交</el-button>
     </div>
   </div>
 </template>

+ 102 - 82
src/views/components/auto/auto-templates/timer-bid.vue

@@ -1,75 +1,11 @@
-<template>
-  <div class="mx-5">
-    <div class="asj-h2">分时调价</div>
-    <div class="mt-3.5">
-      规则执行时区: PDT
-      <div>
-        <el-tag v-if="isVisible" class="custom-tag" closable color="#e7edf4" @close="handleClose">
-          <template #default>
-            <div class="tag-content">
-              <strong>自动化分时规则:</strong>
-              <p>
-                1.
-                应用分时调价后,如需手动修改竞价,只能在此操作。在亚马逊后台或其他第三方系统进行的调价操作,竞价将会被当前时段的自动化执行结果覆盖。
-              </p>
-              <p>2. 广告活动开启分时调价,规则的修改将在下一个整点生效。</p>
-            </div>
-          </template>
-        </el-tag>
-      </div>
-    </div>
-    <el-form ref="formRef" :model="formData" label-position="top" style="margin-top: 20px">
-      <el-form-item :rules="{ required: true, message: '必填项', trigger: 'blur' }" prop="name">
-        <template #label>
-          <span class="asj-h3">模板名称</span>
-        </template>
-        <el-input v-model="formData.name" style="width: 30%"></el-input>
-      </el-form-item>
-      <el-form-item :rules="[{ validator: checkConditions, trigger: 'xxx' }]" prop="rule.conditions">
-        <template #label>
-          <span class="asj-h3">设置竞价</span>
-        </template>
-        <div class="flex flex-col">
-          <div class="flex gap-2 my-2">
-            <el-select v-model="timeRange">
-              <el-option v-for="item in timeRangeOptions" :key="item.value" :label="item.label" :value="item.value" />
-            </el-select>
-            <el-select v-model="schedule">
-              <el-option v-for="item in scheduleOptions" :key="item.value" :label="item.label" :value="item.value" />
-            </el-select>
-            <el-input v-model="bid"
-                      clearable
-                      oninput="value=value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.').replace('.', '$#$').replace(/\./g, '').replace('$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').replace(/^\./g, '')"
-                      placeholder="1.0"
-                      style="width: 150px"
-            ></el-input>
-            <el-button class="active-btn" link style="color: #3c58af"
-                       @click="handleApply">应用
-            </el-button>
-          </div>
-        </div>
-      </el-form-item>
-    </el-form>
-    <TimerBidTable
-        ref="tableRef"
-        :data="formData.rule.conditions"
-        @click="formRef.clearValidate('rule.conditions')" />
-    <div class="auto-page-foot">
-      <el-button style="width: 200px" @click="cancel">取消</el-button>
-      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
-    </div>
-  </div>
-</template>
-
 <script lang="ts" setup>
 /**
  * @Name: timer-bid.vue
  * @Description: 自动化规则模板-分时调价
  * @Author: xinyan
  */
-import { ref, onMounted, Ref } from 'vue';
+import { ref } from 'vue';
 import TimerBidTable from '/@/components/TimerBidTable/index.vue';
-import { userFormData } from '/@/views/components/auto/auto-campaigns/common';
 
 interface Props {
   mode: string;
@@ -81,12 +17,13 @@ const props = defineProps<Props>();
 const emits = defineEmits(['refresh']);
 
 const formRef = ref();
-const formData = ref(props.data)
+const formData = ref(props.data);
+const tableRef = ref(null);
 
 const timeRange = ref('Option1');
 const schedule = ref('Option1');
 const bid = ref('1.0');
-const tableRef = ref(null);
+const bidError = ref(''); // 错误信息
 
 const timeRangeOptions = [
   {
@@ -149,22 +86,17 @@ function handleClose() {
   isVisible.value = false;
 }
 
-const handleApply = () => {
-  if (tableRef.value) {
-    tableRef.value.applyBid(timeRange.value, schedule.value, parseFloat(bid.value));
-  }
-};
+//const checkConditions = (rule: any, value: any, callback: any) => {
+//  for (const bidList of formData.value.rule.conditions) {
+//    for (const val of bidList) {
+//      if (val > 0) {
+//        return callback();
+//      }
+//    }
+//  }
+//  callback(new Error('请先设置竞价!'));
+//};
 
-const checkConditions = (rule: any, value: any, callback: any) => {
-  for (const bidList of formData.value.rule.conditions) {
-    for (const val of bidList) {
-      if (val > 0) {
-        return callback();
-      }
-    }
-  }
-  callback(new Error('请先设置竞价!'));
-};
 const submitForm = async () => {
   formRef.value.validate(async (valid: any) => {
     if (valid) {
@@ -178,8 +110,96 @@ const submitForm = async () => {
 const cancel = () => {
   emits('refresh');
 };
+
+function validateBid(value: string) {
+  if (value === '') {
+    bidError.value = ''; // 空值时不显示错误信息
+  } else if (Number(value) >= 100 || Number(value) <= 0) {
+    bidError.value = '请输入数值大于0且小于100的数值,可精确到小数点后2位';
+  } else {
+    bidError.value = '';
+  }
+}
+
+function handleBidChange(value: string) {
+  validateBid(value);
+}
+
+const handleApply = () => {
+  validateBid(bid.value);
+  if (!bidError.value && tableRef.value) {
+    tableRef.value.applyBid(timeRange.value, schedule.value, parseFloat(bid.value));
+  }
+};
+
 </script>
 
+<template>
+  <div class="mx-5">
+    <div class="asj-h2">分时调价</div>
+    <div class="mt-3.5">
+      规则执行时区: PDT
+      <div>
+        <el-tag v-if="isVisible" class="custom-tag" closable color="#e7edf4" @close="handleClose">
+          <template #default>
+            <div class="tag-content">
+              <strong>自动化分时规则:</strong>
+              <p>
+                1.
+                应用分时调价后,如需手动修改竞价,只能在此操作。在亚马逊后台或其他第三方系统进行的调价操作,竞价将会被当前时段的自动化执行结果覆盖。
+              </p>
+              <p>2. 广告活动开启分时调价,规则的修改将在下一个整点生效。</p>
+            </div>
+          </template>
+        </el-tag>
+      </div>
+    </div>
+    <el-form ref="formRef" :model="formData" label-position="top" style="margin-top: 20px">
+      <el-form-item :rules="{ required: true, message: '必填项', trigger: 'blur' }" prop="name">
+        <template #label>
+          <span class="asj-h3">模板名称</span>
+        </template>
+        <el-input v-model="formData.name" style="width: 30%"></el-input>
+      </el-form-item>
+      <el-form-item prop="rule.conditions">
+        <template #label>
+          <span class="asj-h3">设置竞价</span>
+        </template>
+        <div class="flex flex-col">
+          <div class="flex gap-2 my-2">
+            <el-select v-model="timeRange">
+              <el-option v-for="item in timeRangeOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+            <el-select v-model="schedule">
+              <el-option v-for="item in scheduleOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+            <el-input v-model="bid"
+                      clearable
+                      oninput="value=value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.').replace('.', '$#$').replace(/\./g, '').replace('$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').replace(/^\./g, '')"
+                      placeholder="1.0"
+                      style="width: 150px"
+                      @change="handleBidChange"
+            ></el-input>
+            <div v-if="bidError" style="font-size: 12px;">{{ bidError }}</div>
+            <el-button class="active-btn" link style="color: #3c58af"
+                       @click="handleApply">应用
+            </el-button>
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+    <TimerBidTable
+        ref="tableRef"
+        :data="formData.rule.conditions"
+        @click="formRef.clearValidate('rule.conditions')" />
+    <div class="auto-page-foot">
+      <el-button style="width: 200px" @click="cancel">取消</el-button>
+      <el-button style="width: 200px" type="primary" @click="submitForm">提交</el-button>
+    </div>
+  </div>
+</template>
+
+
 <style scoped>
 .custom-tag {
   height: auto;

+ 29 - 10
src/views/efTools/automation/api.ts

@@ -1,20 +1,21 @@
-import { request } from '/@/utils/service'
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud'
-import XEUtils from 'xe-utils'
+import { request } from '/@/utils/service';
+import { AddReq, DelReq, EditReq, UserPageQuery } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/auto_plan_template/';
 
-export const apiPrefix = '/api/ad_manage/auto_plan_template/'
 export function GetList(query: UserPageQuery) {
   return request({
     url: apiPrefix,
     method: 'get',
     params: query,
-  })
+  });
 }
+
 export function GetObj(id: number) {
   return request({
     url: apiPrefix + id,
     method: 'get',
-  })
+  });
 }
 
 export function AddObj(obj: AddReq) {
@@ -22,7 +23,7 @@ export function AddObj(obj: AddReq) {
     url: apiPrefix,
     method: 'post',
     data: obj,
-  })
+  });
 }
 
 export function UpdateObj(obj: EditReq) {
@@ -30,7 +31,7 @@ export function UpdateObj(obj: EditReq) {
     url: apiPrefix + obj.id + '/',
     method: 'put',
     data: obj,
-  })
+  });
 }
 
 export function DelObj(id: DelReq) {
@@ -38,7 +39,7 @@ export function DelObj(id: DelReq) {
     url: apiPrefix + id + '/',
     method: 'delete',
     data: { id },
-  })
+  });
 }
 
 //关联广告活动
@@ -47,5 +48,23 @@ export function getRelationCampaign(query) {
     url: '/api/ad_manage/template/relation_campaign_list/',
     method: 'GET',
     params: query,
-  })
+  });
+}
+
+//广告组合下拉框
+export function getAdGroupList(query) {
+  return request({
+    url: '/api/ad_manage/ad_tree/portfolios',
+    method: 'GET',
+    params: query,
+  });
+}
+
+//广告关联活动保存
+export function updateAdCampaign(body) {
+  return request({
+    url: '/api/ad_manage/template/relation_campaign_enable/',
+    method: 'POST',
+    data: body,
+  });
 }

+ 236 - 99
src/views/efTools/automation/components/adActivityDialog.vue

@@ -4,76 +4,79 @@
  * @Description:广告关联活动弹窗
  * @Author: xinyan
  */
-import { inject, onMounted, Ref, ref, watch } from 'vue';
-import { getRelationCampaign } from '/@/views/efTools/automation/api';
+import { onMounted, ref, watch } from 'vue';
+import { getAdGroupList, getRelationCampaign } from '/@/views/efTools/automation/api';
 import { storeToRefs } from 'pinia';
 import { useShopInfo } from '/@/stores/shopInfo';
+import { ElMessage } from 'element-plus';
 
 const props = defineProps({
   templateId: {
     type: [String, Number],
     required: true,
-  }
+  },
+  modelValue: {
+    type: Boolean,
+    required: true,
+  },
 });
-const shopInfo = useShopInfo()
-const { profile } = storeToRefs(shopInfo)
+const emits = defineEmits(['update:modelValue']);
+const shopInfo = useShopInfo();
+const { profile } = storeToRefs(shopInfo);
 const { templateId } = toRefs(props);
-console.log(templateId);
 
-const dialogVisible = inject<Ref>('isDialogVisible');
-//广告活动输入框
+const dialogVisible = ref(props.modelValue);
+//筛选条件
 const searchAdCampaign = ref('');
 const selectedCampaignType = ref('sp');
 const selectedAdGroup = ref('');
 const selectedStatus = ref('');
-const selectedActivityTag = ref('');
-const currentPage = ref('');
-const total = ref('');
-const pageSize = ref(20);
-const loading = ref(false);
-const handleSizeChange = (size) => {
-  pageSize.value = size;
-  fetchAdCampaign(); // 调用 fetchAdCampaign 重新获取数据
-};
-
-const handleCurrentChange = (newPage) => {
-  currentPage.value = newPage;
-  loading.value = true;
-  fetchAdCampaign();
-  loading.value = false;
-};
 
+const adCampaignName = ref([]);
 const campaignType = [
-  {value: 'sb', label: 'SB'},
-  {value: 'sp', label: 'SP'},
-  {value: 'sd', label: 'SD'},
+  { value: 'sb', label: 'SB' },
+  { value: 'sp', label: 'SP' },
+  { value: 'sd', label: 'SD' },
 ];
-
-const adGroups = [
-  {value: 'Group1', label: 'Group1'},
-  {value: 'Group2', label: 'Group2'}
-];
-
+const adGroups = ref([]);
 const campaignStatus = [
-  {value: 'active', label: 'Active'},
-  {value: 'inactive', label: 'Inactive'}
+  { value: 0, label: '未存档' },
+  { value: 'ENABLED', label: '已启用' },
+  { value: 'PAUSED', label: '已暂停' },
 ];
 
-const activityTags = [
-  {value: 'Tag1', label: 'Tag1'},
-  {value: 'Tag2', label: 'Tag2'}
-];
+//表格
+const currentPage = ref(1);
+const pageSize = ref(25);
+const total = ref(0);
 
-const adCampaignName = ref([]);
+const loading = ref(false);
+const refTable = ref(null);
+const selectedAds = ref([]);
+let selections = []; // 添加选中的项
+
+function handleCurrentChange(newPage) {
+  currentPage.value = newPage;
+  loading.value = true;
+  fetchAdCampaign();
+}
+
+// 处理分页器每页显示条目数变化
+function handleSizeChange(newSize) {
+  pageSize.value = newSize;
+  currentPage.value = 1;
+  fetchAdCampaign();
+}
 
 async function fetchAdCampaign() {
   try {
+    loading.value = true;
     const resp = await getRelationCampaign({
       profileId: profile.value.profile_id,
       templateId: templateId.value,
-      campaignName: '',
-      portfolioId: '',
-      campaignStatus: '',
+      campaignName: searchAdCampaign.value,
+      portfolioId: selectedAdGroup.value,
+      campaignStatus: selectedStatus.value,
       campaignType: selectedCampaignType.value,
       page: currentPage.value,
       limit: pageSize.value,
@@ -81,44 +84,155 @@ async function fetchAdCampaign() {
     adCampaignName.value = resp.data;
     total.value = resp.total;
     currentPage.value = resp.page;
+    // 恢复勾选状态
+    nextTick(() => {
+      adCampaignName.value.forEach(item => {
+        if (selectedAds.value.some(ad => ad.campaignId === item.campaignId)) {
+          refTable.value.toggleRowSelection(item, true);
+        }
+      });
+    });
   } catch (error) {
-    console.error('Error fetching task data:', error);
+    ElMessage.error('请求广告活动数据失败');
+  } finally {
+    loading.value = false;
   }
 }
 
-const selectedAds = ref([]);
-
-const handleSelectionChange = (selection) => {
-  selectedAds.value = selection;
-  console.log('Selected ads:', selectedAds.value);
-};
+function handleSelectionChange(selection) {
+  selections = selection;
+  const newSelections = selections.filter(
+      (sel) => !selectedAds.value.some((added) => added.campaignId === sel.campaignId)
+  );
+  if (newSelections.length > 0) {
+    selectedAds.value.push(...newSelections);
+  }
+  // 处理取消选中的项
+  const removedSelections = selectedAds.value.filter(
+      (added) => !selections.some((sel) => sel.campaignId === added.campaignId)
+  );
 
-const confirm = () => {
-};
+  if (removedSelections.length > 0) {
+    selectedAds.value = selectedAds.value.filter(
+        (added) => !removedSelections.some((removed) => removed.campaignId === added.campaignId)
+    );
+  }
+}
 
-const removeSelectedAd = (index) => {
-  selectedAds.value.splice(index, 1);
-};
+function removeSelectedAd(index) {
+  const removedAd = selectedAds.value.splice(index, 1)[0];
+  const targetIndex = adCampaignName.value.findIndex(ad => ad.campaignName === removedAd.campaignName);
+  if (targetIndex !== -1) {
+    const adTable = refTable.value;
+    adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
+  }
+}
 
-const removeAllSelectedAds = () => {
+function removeAllSelectedAds() {
+  const adTable = refTable.value;
+  selectedAds.value.forEach(ad => {
+    const targetIndex = adCampaignName.value.findIndex(item => item.campaignName === ad.campaignName);
+    if (targetIndex !== -1) {
+      adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
+    }
+  });
   selectedAds.value = [];
-};
+}
+
+function cancel() {
+  dialogVisible.value = false;
+}
+
+//TODO: 确认按钮
+async function confirm() {
+  const campaignItems = selectedAds.value.map(ad => ({
+    campaignId: ad.campaignId,
+    campaignType: ad.campaignType
+  }));
+  const adGroupInfo = [];
+  const campaignTargetInfo = [
+    { targetId: '492707808377423', adGroup_id: '448117369011017', bid: 0.45 },
+  ];
+  const campaignKeywordInfo = [
+    { keywordId: '416969576305724', adGroup_id: '393554556566271', bid: 0.04 },
+  ];
+
+  const requestData = {
+    profileId: profile.value.profile_id,
+    templateId: templateId.value,
+    campaignItems,
+    adGroupInfo,
+    campaignTargetInfo,
+    campaignKeywordInfo
+  };
+  //console.log('requestData', requestData);
+}
+
+// 获取广告组下拉框
+async function fetchAdGroupList() {
+  try {
+    const resp = await getAdGroupList({
+      profileId: profile.value.profile_id,
+    });
+    adGroups.value = resp.data.map((item: any) => {
+      return {
+        label: item.name,
+        value: item.portfolioId
+      };
+    });
+  } catch (error) {
+    ElMessage.error('请求失败');
+  }
+}
 
 const headerCellStyle = (args) => {
   if (args.rowIndex === 0) {
     return {
       backgroundColor: 'rgba(245, 245, 245, 0.9)',
+      fontWeight: '500',
     };
   }
 };
 
+const cellStyle = () => {
+  return {
+    fontSize: '13px',
+    //fontWeight: '500',
+  };
+};
+
+//监控筛选条件变化
 watch(selectedCampaignType, () => {
   fetchAdCampaign();
 });
 
-watch(templateId,()=>{
+watch(selectedAdGroup, (newVal) => {
+  if (newVal) {
+    selectedAdGroup.value = newVal;
+    fetchAdCampaign();
+  }
+});
+
+watch(selectedStatus, () => {
+  fetchAdCampaign();
+});
+
+watch(templateId, () => {
   fetchAdCampaign();
-})
+  fetchAdGroupList();
+});
+
+watch(() => props.modelValue, (newValue) => {
+  dialogVisible.value = newValue;
+});
+
+watch(dialogVisible, (newValue) => {
+  emits('update:modelValue', newValue);
+});
+
+onMounted(() => {
+  //fetchAdGroupList();
+});
 
 </script>
 
@@ -130,9 +244,10 @@ watch(templateId,()=>{
       title="关联广告活动"
       width="1158px"
   >
-    <div class="custom-border" style="display: flex;">
+    <div style="display: flex;">
       <div style="width: 50%;">
-        <el-input v-model="searchAdCampaign" placeholder="请输入广告活动" style="width: 100%;"></el-input>
+        <el-input v-model="searchAdCampaign" placeholder="请输入广告活动" style="width: 100%;"
+                  @change="fetchAdCampaign()"></el-input>
         <div class="custom-inline">
           <el-select v-model="selectedCampaignType" placeholder="选择广告类型">
             <el-option v-for="item in campaignType"
@@ -159,18 +274,11 @@ watch(templateId,()=>{
                 :value="item.value"
             ></el-option>
           </el-select>
-
-          <el-select v-model="selectedActivityTag" placeholder="活动标签">
-            <el-option
-                v-for="item in activityTags"
-                :key="item.value"
-                :label="item.label"
-                :value="item.value"
-            ></el-option>
-          </el-select>
         </div>
         <el-table
+            ref="refTable"
             v-loading="loading"
+            :cell-style="cellStyle"
             :data="adCampaignName"
             :header-cell-style="headerCellStyle"
             height="400"
@@ -180,46 +288,65 @@ watch(templateId,()=>{
           <el-table-column label="广告活动名称" prop="campaignName">
             <template #default="scope">
               <el-tag
-                  :type="scope.row.campaignType === 'SB' ? 'primary' : 'success'"
+                  :color="scope.row.campaignType === 'sb' ? '#0163d2' : (scope.row.campaignType === 'sp' ? '#ff7424' : '#365672')"
+                  class="campaign-type"
                   disable-transitions
-                  round
-              >
+                  round>
                 {{ scope.row.campaignType }}
               </el-tag>
-              {{ scope.row.campaignName }}
+              {{ scope.row.campaignName }}
             </template>
           </el-table-column>
           <el-table-column type="selection" width="55"></el-table-column>
         </el-table>
-        <el-pagination
-            v-model:current-page="currentPage"
-            :page-size="pageSize"
-            :page-sizes="[10, 25, 50]"
-            :total="total"
-            layout="total, prev, pager, next"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-        ></el-pagination>
+        <div class="pagination-container mt-4">
+          <el-pagination
+              v-model:current-page="currentPage"
+              :page-size="pageSize"
+              :page-sizes="[10, 25, 50,100,200]"
+              :total="total"
+              background
+              layout="total,sizes,prev, next, jumper"
+              small
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+          />
+        </div>
       </div>
 
       <div style="flex: 1; padding-left: 20px;">
         <h3>已选择({{ selectedAds.length }})</h3>
         <el-table
+            :cell-style="cellStyle"
             :data="selectedAds"
             :header-cell-style="headerCellStyle"
             height="484"
             style="width: 100%; margin-top: 20px;"
         >
-          <el-table-column label="广告活动" prop="campaignName"></el-table-column>
+          <el-table-column label="广告活动" prop="campaignName">
+            <template #default="scope">
+              <el-tag
+                  :color="scope.row.campaignType === 'sb' ? '#0163d2' : (scope.row.campaignType === 'sp' ? '#ff7424' : '#365672')"
+                  class="campaign-type"
+                  disable-transitions
+                  round>
+                {{ scope.row.campaignType }}
+              </el-tag>
+              {{ scope.row.campaignName }}
+            </template>
+          </el-table-column>
           <el-table-column
+              align="center"
               label="操作"
               width="100"
           >
             <template #header>
-              <el-button link size="normal" type="danger" @click="removeAllSelectedAds">删除全部</el-button>
+              <el-button link size="default" style="color: #2077d7" @click="removeAllSelectedAds">删除全部</el-button>
             </template>
             <template #default="scope">
-              <el-button type="text" @click="removeSelectedAd(scope.$index)">删除</el-button>
+              <el-button type="text" @click="removeSelectedAd(scope.$index)">
+                <CircleClose style="width:16px;color:#4b5765" />
+              </el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -227,7 +354,7 @@ watch(templateId,()=>{
     </div>
     <template #footer>
       <div class="dialog-footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button @click="cancel">取消</el-button>
         <el-button type="primary" @click="confirm">确定</el-button>
       </div>
     </template>
@@ -235,19 +362,29 @@ watch(templateId,()=>{
 </template>
 
 <style scoped>
-.SP {
-  background-color: orange;
-  color: white;
-  padding: 2px 4px;
-  border-radius: 3px;
-  margin-right: 8px;
+.pagination-container {
+  display: flex;
+  flex-direction: row-reverse;
 }
 
-.SB {
-  background-color: blue;
-  color: white;
-  padding: 2px 4px;
-  border-radius: 3px;
-  margin-right: 8px;
+.custom-inline {
+  display: flex;
+  justify-content: space-around;
+  margin: 12px 0;
+  gap: 4px;
 }
+
+.campaign-type {
+  width: 35px;
+  text-align: center;
+  height: 22px;
+  font-size: 13px;
+  font-weight: 400;
+  color: #fff;
+  border-color: #fff;
+  line-height: 22px;
+  border-radius: 12px;
+  margin-right: 4px;
+}
+
 </style>

+ 69 - 0
src/views/efTools/automation/components/automatedRuleTips.vue

@@ -0,0 +1,69 @@
+<script lang="ts" setup>
+/**
+ * @Name: automatedRuleTips.vue
+ * @Description: 自动化规则提示
+ * @Author: xinyan
+ */
+import {ref} from 'vue';
+
+const props = defineProps({
+  drawerVisible: {
+    type: Boolean,
+    default: false,
+  }
+});
+const drawerVisible = ref(props.drawerVisible);
+const activeNames = ref(['1']);
+
+</script>
+
+<template>
+  <el-drawer v-model="drawerVisible" :close-on-click-modal="false" :destroy-on-close="true"
+             size="30%"
+             title="如何使用自动化规则?"
+             style="color: #0b0d0d;font-weight: 600;">
+    <div class="description">
+      <p>
+        自动化规则提供分时调价、开启/暂停广告活动、添加关键词、添加否定词模板,您可以根据您的需求选择对应模板并进行设置,关联具体的广告活动。</p>
+    </div>
+    <div style="padding: 20px;">
+      <el-collapse v-model="activeNames">
+        <el-collapse-item name="1" title="分时调价">
+          <p>设置分时条件模板可以精确到一周每天每小时的颗粒度设置您的竞价,可以应用到您关联的每个广告活动。</p>
+        </el-collapse-item>
+        <el-collapse-item name="2" title="开启/暂停广告活动">
+          <p>设置开启暂停广告活动模板支持您自定义设置开启和暂停操作的条件,并应用到关联的广告活动。</p>
+        </el-collapse-item>
+        <el-collapse-item name="3" title="添加关键词">
+          <p>
+            设置添加关键词模版,根据自定义数据指标,将对应搜索词(含关键词、商品),添加到指定广告组进行投放;将模版应用到关联的广告活动时,表示自定义数据指标将从这些广告活动中获取。</p>
+        </el-collapse-item>
+        <el-collapse-item name="4" title="添加否定词">
+          <p>根据自定义数据指标,添加否定词到指定的广告组中。</p>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+  </el-drawer>
+</template>
+
+<style scoped>
+.description {
+  padding: 20px 20px 0;
+  font-size: 13px;
+  color: #666;
+  margin: 0;
+}
+
+::v-deep .el-collapse-item__header {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.el-collapse-item__content p {
+  font-size: 13px;
+  color: #666;
+  margin: 0;
+}
+
+</style>

+ 0 - 142
src/views/efTools/automation/crud.tsx

@@ -1,142 +0,0 @@
-import * as api from './api'
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
-import { inject, nextTick, ref } from 'vue'
-import { TemplateType } from '../utils/enum'
-
-export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-
-  const { editTmpl, showDialog } = context;
-
-  const pageRequest = async (query: UserPageQuery) => {
-    return await api.GetList(query)
-  }
-  const editRequest = async ({ form, row }: EditReq) => {
-    form.id = row.id
-    return await api.UpdateObj(form)
-  }
-  const delRequest = async ({ row }: DelReq) => {
-    return await api.DelObj(row.id)
-  }
-  const addRequest = async ({ form }: AddReq) => {
-    return await api.AddObj(form)
-  }
-
-  //权限判定
-  const hasPermissions = inject('$hasPermissions')
-
-  return {
-    crudOptions: {
-      table: {
-        height: 900,
-        size: 'default',
-        border: false,
-      },
-      container: {
-        fixedHeight: false,
-      },
-      request: {
-        pageRequest,
-        addRequest,
-        editRequest,
-        delRequest,
-      },
-      rowHandle: {
-        fixed: 'right',
-        width: 100,
-        buttons: {
-          view: {
-            show: false,
-          },
-          edit: {
-          	text: null,
-            iconRight: 'Edit',
-          	type: 'text',
-          	// show: hasPermissions('dictionary:Update'),
-            order: 1,
-            tooltip: {
-              placement: 'top',
-              content: '编辑',
-            },
-            click: (ctx: any) => {
-              editTmpl(ctx.row)
-            }
-          },
-          custom: {
-            text: null,
-            iconRight: 'SetUp',
-            type: 'text',
-            order: 2,
-            tooltip: {
-              placement: 'top',
-              content: '关联广告活动',
-            },
-            click: (ctx: any) => {
-              showDialog(); // 调用传入的上下文中的 showDialog 函数
-            }
-          },
-          remove: {
-            iconRight: 'Delete',
-            type: 'text',
-            text: null,
-            order: 3,
-            tooltip: {
-          		placement: 'top',
-          		content: '删除',
-          	}
-            // show: hasPermissions('dictionary:Delete'),
-          },
-        },
-      },
-      actionbar: {
-        show: true,
-        buttons: {
-          add: {
-            show: false,
-          }
-        }
-      },
-      columns: {
-        id: {
-          title: 'ID',
-          column: {
-            width: 100,
-            align: 'center',
-          },
-        },
-        name: {
-          title: '模板名称',
-          column: {
-            width: '150px',
-          },
-          search: {
-            show: false
-          }
-        },
-        "rule.type": {
-          title: '模板类型',
-          type: 'dict-select',
-          dict: dict({
-            data: TemplateType
-          }),
-          search: {
-            show: true,
-            component: {
-              props: {
-                clearable: true,
-              },
-            },
-          }
-        },
-        campaignNumber: {
-          title: '广告活动数量'
-        },
-        creator_username: {
-          title: '创建人'
-        },
-        modifier_username: {
-          title: '修改人'
-        }
-      }
-    }
-  }
-}

+ 156 - 74
src/views/efTools/automation/index.vue

@@ -1,47 +1,8 @@
-<template>
-  <div class="mx-3 mt-3" style="display: flex; gap: 14px;">
-    <el-input v-model="templateList" :prefix-icon="Search" placeholder="快速查询" style="width: 240px" @change=""></el-input>
-    <el-select v-model="templateType" placeholder="Select" style="width: 240px">
-      <el-option
-          v-for="item in TemplateType"
-          :key="item.value"
-          :label="item.label"
-          :value="item.value"
-      />
-    </el-select>
-  </div>
-  <el-card class="mx-3 my-3">
-    <vxe-grid v-bind="gridOptions" v-on="gridEvents">
-      <template #toolbar_buttons>
-        <SelectBotton :options="TemplateType" btn-title="新建模板" @click="createTmpl"></SelectBotton>
-      </template>
-      <template #operate="{ row }">
-        <el-button icon="Edit" type="text" @click="editTmpl(row)"></el-button>
-        <el-button icon="SetUp" type="text" @click="showDialog" :disabled="true"></el-button>
-        <el-button icon="Delete" type="text" @click=""></el-button>
-      </template>
-    </vxe-grid>
-  </el-card>
-  <el-drawer v-model="showDrawer" :close-on-click-modal="false" :destroy-on-close="true"
-             :title="mode === 'add' ? '新建模板' : '编辑模板'"
-             size="70%">
-    <div style="padding: 0 15px">
-      <component
-          :is="dyComponents[formData.rule.type]"
-          :data="formData"
-          :mode="mode"
-          :submitFormData="submitFormData"
-          @refresh="refreshTable"></component>
-    </div>
-  </el-drawer>
-  <AdActivityDialog v-model:visible="isDialogVisible" />
-</template>
-
 <script lang="ts" setup>
-import { onMounted, provide, reactive, ref, Ref } from 'vue';
+import { onMounted, provide, reactive, ref, Ref, watch } from 'vue';
 import { Search } from '@element-plus/icons-vue';
 import { TemplateType } from '../utils/enum';
-import { GetList } from '/@/views/efTools/automation/api';
+import { DelObj, GetList } from '/@/views/efTools/automation/api';
 import SelectBotton from '/@/components/select-button/index.vue';
 import TimerBidTmpl from '/@/views/components/auto/auto-templates/timer-bid.vue';
 import TimerBudgetTmpl from '/@/views/components/auto/auto-templates/timer-budget.vue';
@@ -51,21 +12,23 @@ import SearchTermTmpl from '/@/views/components/auto/auto-templates/search-term.
 import NegKeywordTmpl from '/@/views/components/auto/auto-templates/neg-keyword.vue';
 import AdActivityDialog from './components/adActivityDialog.vue';
 import { AddObj, UpdateObj } from './api';
+import AutomatedRuleTips from '/@/views/efTools/automation/components/automatedRuleTips.vue';
 
+//自动化规则提示
+const autoInfo = ref(false);
+//关联广告活动弹窗
 const isDialogVisible = ref(false);
+const templateId = ref('');
+provide('isDialogVisible', isDialogVisible);
 
-const showDialog = () => {
-  isDialogVisible.value = true;
-};
-provide('isDialogVisible',isDialogVisible)
 //查询
 const templateList = ref('');
 const templateType = ref('');
 
 //创建,编辑
+const CreateTemplateOptions = ref(TemplateType.filter(item => item.value !== ''));
 const mode = ref('');
 const showDrawer = ref(false);
-
 const formData: Ref<AutoTemplate> = ref({
   name: '',
   rule: {
@@ -89,12 +52,21 @@ const dyComponents = {
   6: NegKeywordTmpl,
 };
 
-const refreshTable = () => {
+function refreshTable() {
   showDrawer.value = false;
   getList();
+}
+
+const showDialog = (row: any) => {
+  templateId.value = row.id;
+  isDialogVisible.value = true;
 };
 
-const createTmpl = (val: number) => {
+function handleClick() {
+  autoInfo.value = true;
+}
+
+function createTmpl(val: number) {
   mode.value = 'add';
   delete formData.value.id;
   formData.value.name = '';
@@ -108,35 +80,40 @@ const createTmpl = (val: number) => {
     weekdays: [],
     conditions: [],
   };
-  //console.log(formData.value);
   showDrawer.value = true;
-};
+}
 
-const editTmpl = (row: any) => {
+function editTmpl(row: any) {
   mode.value = 'edit';
   formData.value.id = row.id;
   formData.value.name = row.name;
   formData.value.rule = row.rule;
   showDrawer.value = true;
-};
+}
+
+async function deleteTmpl(row: any) {
+  await DelObj(row.id);
+  await getList();
+}
 
-const submitFormData = async () => {
+async function submitFormData() {
   if (mode.value === 'add') {
     await AddObj(formData.value);
   } else if (mode.value === 'edit') {
     await UpdateObj(formData.value);
   }
   refreshTable();
-};
+}
 
 //表格配置
 const gridOptions = reactive({
-  border: 'inner',
-  height: 900,
+  //border: 'inner',
+  height: 820,
   align: null,
   loading: false,
   rowConfig: {
     isHover: true,
+    height: 45
   },
   columnConfig: {
     resizable: true,
@@ -149,13 +126,20 @@ const gridOptions = reactive({
     pageSizes: [10, 20, 30],
   },
   columns: [
-    {field: 'id', title: 'ID'},
-    {field: 'name', title: '模板名称'},
-    {field: 'rule.type', title: '模板类型', formatter: ({cellValue}) => getTemplateTypeLabel(cellValue)},
-    {field: 'campaignNumber', title: '广告活动数量'},
-    {field: 'creator_username', title: '创建人'},
-    {field: 'modifier_username', title: '修改人'},
-    {title: '操作', width: 120, slots: {default: 'operate'}},
+    { field: 'id', title: 'ID' ,width:140},
+    { field: 'name', title: '模板名称' },
+    {
+      field: 'rule.type',
+      title: '模板类型',
+      formatter: ({ cellValue }) => getTemplateTypeLabel(cellValue),
+      slots: { default: 'type' }
+    },
+    { field: 'campaignNumber', title: '广告活动数量' },
+    { field: 'creator_username', title: '创建人' },
+    //{ field: 'createTime', title: '创建时间' },
+    { field: 'modifier_username', title: '更新人' },
+    //{ field: 'updateTime', title: '更新时间' },
+    { title: '操作', width: 120, slots: { default: 'operate' } },
   ],
   toolbarConfig: {
     slots: {
@@ -166,7 +150,7 @@ const gridOptions = reactive({
 });
 
 const gridEvents = {
-  pageChange({currentPage, pageSize}) {
+  pageChange({ currentPage, pageSize }) {
     if (gridOptions.pagerConfig) {
       gridOptions.pagerConfig.currentPage = currentPage;
       gridOptions.pagerConfig.pageSize = pageSize;
@@ -175,21 +159,26 @@ const gridEvents = {
   },
 };
 
+watch(templateType, () => {
+  getList();
+});
+
 async function getList() {
   try {
     gridOptions.loading = true;
     const response = await GetList({
       page: gridOptions.pagerConfig.currentPage,
       limit: gridOptions.pagerConfig.pageSize,
+      type: templateType.value,
+      name: templateList.value,
     });
     gridOptions.data = response.data.map(item => ({
       ...item,
       rule: {
         ...item.rule,
-        typeLabel: getTemplateTypeLabel(item.rule.type),
+        typeLabelWithColor: getTemplateTypeLabel(item.rule.type),
       },
     }));
-    console.log(response.data);
     gridOptions.pagerConfig.total = response.total;
   } catch (error) {
     console.error('Error fetching task data:', error);
@@ -200,22 +189,114 @@ async function getList() {
 
 function getTemplateTypeLabel(type: number) {
   const template = TemplateType.find(t => t.value === type);
-  return template ? template.label : '';
+  if (template) {
+    return { label: template.label, type: type };
+  }
+  return { label: '', type: '' };
+}
+
+function getTagType(type) {
+  switch (type) {
+    case 1:
+      return 'danger'; // 分时调价
+    case 2:
+      return 'success'; // 分时预算
+    case 3:
+      return 'info'; // 广告活动
+    case 4:
+      return 'warning'; // 定向规则
+    case 5:
+      return 'primary'; // 添加搜索词
+    case 6:
+      return ''; // 添加否定词
+    default:
+      return '';
+  }
 }
 
+const cellStyle = () => {
+  return {
+    fontSize: '13px',
+    //fontWeight: '500',
+  };
+};
+
+const headerCellStyle = () => {
+  return {
+    fontSize: '14px',
+    fontWeight: '500',
+    color: '#2d2d2d',
+  };
+};
+
 onMounted(() => {
   getList();
 });
 </script>
 
-<style>
-.custom-inline {
-  display: flex;
-  justify-content: space-around;
-  margin: 12px 0;
-  gap: 4px;
-}
+<template>
+  <div class="flex" style="justify-content: space-between">
+    <div class="mx-3 mt-3" style="display: flex; gap: 14px;">
+      <el-input v-model="templateList"
+                :prefix-icon="Search"
+                clearable
+                placeholder="模板名称"
+                style="width: 240px"
+                @change="getList"
+      />
+      <el-select v-model="templateType" placeholder="Select" style="width: 240px">
+        <el-option
+            v-for="item in TemplateType"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+        />
+      </el-select>
+    </div>
+    <div class="mx-3 mt-3" style="display: flex; gap: 14px;">
+      <el-button link style="color: #2b7ed9;font-size: 12px" @click="handleClick">
+        <el-icon>
+          <question-filled />
+        </el-icon>
+        如何使用自动化规则?
+      </el-button>
+    </div>
+  </div>
+  <el-card class="mx-3 my-3">
+    <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle" show-overflow v-bind="gridOptions"
+              v-on="gridEvents">
+      <template #toolbar_buttons>
+        <SelectBotton :options="CreateTemplateOptions" btn-title="新建模板" @click="createTmpl"></SelectBotton>
+      </template>
+      <template #operate="{ row }">
+        <el-button icon="Edit" style="color: #0b52a7" type="text" @click="editTmpl(row)"></el-button>
+        <el-button icon="SetUp" style="color: #0b52a7" type="text" @click="showDialog(row)"></el-button>
+        <el-button icon="Delete" style="color: #0b52a7" type="text" @click="deleteTmpl(row)"></el-button>
+      </template>
+      <template #type="{row}">
+        <!--<el-tag :type="getTagType(row.rule.typeLabelWithColor.type)">-->
+        {{ row.rule.typeLabelWithColor.label }}
+        <!--</el-tag>-->
+      </template>
+    </vxe-grid>
+  </el-card>
+  <el-drawer v-model="showDrawer" :close-on-click-modal="false" :destroy-on-close="true"
+             :title="mode === 'add' ? '新建模板' : '编辑模板'"
+             size="70%">
+    <div style="padding: 0 15px">
+      <component
+          :is="dyComponents[formData.rule.type]"
+          :data="formData"
+          :mode="mode"
+          :submitFormData="submitFormData"
+          @refresh="refreshTable"></component>
+    </div>
+  </el-drawer>
+  <AdActivityDialog v-model="isDialogVisible" :templateId="templateId" />
+  <AutomatedRuleTips v-model="autoInfo"></AutomatedRuleTips>
+</template>
 
+<style>
 .custom-dialog .el-dialog__header {
   height: 60px;
   line-height: 60px;
@@ -224,4 +305,5 @@ onMounted(() => {
   border-bottom: 1px solid #e5e6eb;
   border-radius: 10px 10px 0 0;
 }
+
 </style>

+ 2 - 1
src/views/efTools/utils/enum.ts

@@ -1,8 +1,9 @@
 export const TemplateType = [
+  { label: '所有类型', value: '' },
   { label: '分时调价', value: 1 },
   { label: '分时预算', value: 2 },
   { label: '广告活动', value: 3 },
   { label: '定向规则', value: 4 },
   { label: '添加搜索词', value: 5 },
   { label: '添加否定词', value: 6 },
-]
+];

+ 4 - 4
src/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue

@@ -118,7 +118,7 @@ async function emitChange() {
   localStorage.setItem('brandNameList', JSON.stringify(brandNameList.value));
 }
 
-watch([platformNumberList, platformNameList, operationList, countryList, brandNameList, usersList], () => {
+watch([operationList, countryList, brandNameList, usersList], () => {
   emitChange();
 });
 
@@ -133,9 +133,9 @@ defineExpose({ fetchFilteredData, filteredData, updateData });
 
 <template>
   <div class="flex-container">
-    <el-input v-model="platformNumberList" @change="emitChange" placeholder="平台编号" class="flex-item"></el-input>
-    <el-input v-model="platformNameList" @change="emitChange" placeholder="平台名称" class="flex-item"></el-input>
-    <el-input v-model="operationList" @change="emitChange" placeholder="运营" class="flex-item"></el-input>
+    <el-input v-model="platformNumberList" @change="emitChange" placeholder="平台编号" class="flex-item" clearable></el-input>
+    <el-input v-model="platformNameList" @change="emitChange" placeholder="平台名称" class="flex-item" clearable></el-input>
+    <el-input v-model="operationList" @change="emitChange" placeholder="运营" class="flex-item" clearable></el-input>
     <el-select v-if="props.showOperationSearch" v-model="usersList" multiple collapse-tags collapse-tags-tooltip placeholder="填写人" class="flex-item">
       <el-option v-for="item in usersOptions" :key="item.value" :label="item.label" :value="item.value" />
     </el-select>