ソースを参照

✨ feat<自动化规则>: 自动化规则页面布局UI更新

WanGxC 11 ヶ月 前
コミット
f7d6874173

+ 127 - 121
src/components/TimerBidTable/index.vue

@@ -1,69 +1,20 @@
-<template>
-  <div class="calendar">
-    <table class="calendar-table calendar-table-hour">
-      <thead class="calendar-head">
-        <tr>
-          <th rowspan="8" class="week-td">星期 / 时间</th>
-          <th colspan="12">00:00 - 12:00</th>
-          <th colspan="12">12:00 - 24:00</th>
-          <th colspan="4" rowspan="2" class="week-td" style="display: none">小时</th>
-        </tr>
-        <tr>
-          <th colspan="1" v-for="(_, i) in 24" :key="i">{{ i }}</th>
-        </tr>
-      </thead>
-      <tbody>
-        <template v-for="(hoursList, week) in items">
-          <tr>
-            <th>{{ WeekMap[week] }}</th>
-            <td
-              v-for="(info, hour) in hoursList"
-              :key="hour"
-              @mousedown="handleMouseDown(week, hour, $event)"
-              @mouseover="handleMouseMove(week, hour)"
-              @mouseup="handleMouseUp"
-              :style="getTdStyle(info)">
-              <span class="cell-text"> {{ info.value ? info.value : '' }} </span>
-            </td>
-          </tr>
-        </template>
-        <tr>
-          <th colspan="28" class="clear-bar">
-            <span class="middle">可拖动鼠标选择时间段</span>
-            <el-button class="hover-link fr" link @click="resetAllBid" :disabled="disabled">全部重置</el-button>
-          </th>
-        </tr>
-      </tbody>
-    </table>
-
-    <el-dialog v-model="dialogVisible" title="修改出价系数" width="30%" :close-on-click-modal="false" :before-close="closeDialog">
-      <el-input-number v-model="bid" :min="0" :max="100" :step="0.1" controls-position="right" />
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelBid">取消</el-button>
-          <el-button type="primary" @click="submitBid">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
 <script lang="ts" setup>
-import { ref, onMounted, watch, reactive } from 'vue'
+import { ref, onMounted, watch, reactive } from 'vue';
 
 interface Props {
-  data: number[][]
-  disabled: boolean
+  data: number[][];
+  disabled: boolean;
 }
+
 const props = withDefaults(defineProps<Props>(), {
   disabled: false,
-})
-const bidData = ref(props.data)
+});
+const bidData = ref(props.data);
 
-const mouseDown = ref(false)
-const startRowIndex = ref(0)
-const startColIndex = ref(0)
-const items = ref([])
+const mouseDown = ref(false);
+const startRowIndex = ref(0);
+const startColIndex = ref(0);
+const items = ref([]);
 const WeekMap = {
   0: '星期一',
   1: '星期二',
@@ -72,25 +23,25 @@ const WeekMap = {
   4: '星期五',
   5: '星期六',
   6: '星期日',
-}
-const dialogVisible = ref(false)
-const bid = ref(0)
-const selectedItems = ref({})
+};
+const dialogVisible = ref(false);
+const bid = ref(0);
+const selectedItems = ref({});
 
-const getTdStyle = (info: any) => {
+function getTdStyle(info: any) {
   if (props.disabled) {
-    return { cursor: 'not-allowed', background: '#fff' }
+    return { cursor: 'not-allowed', background: '#fff' };
   }
-  return { background: info.selected ? '#ccdbff' : '' }
+  return { background: info.selected ? '#ccdbff' : '' };
 }
 
 for (let i = 0; i < 7; i++) {
-  selectedItems.value[i] = []
-  const tmp = []
+  selectedItems.value[i] = [];
+  const tmp = [];
   for (let j = 0; j < 24; j++) {
-    tmp.push({ value: bidData.value.length === 0 ? 0 : bidData.value[i][j], selected: false })
+    tmp.push({ value: bidData.value.length === 0 ? 0 : bidData.value[i][j], selected: false });
   }
-  items.value.push(tmp)
+  items.value.push(tmp);
 }
 
 watch(
@@ -99,113 +50,166 @@ watch(
     // console.log(100, 'watch', props.data)
     if (props.data.length === 0) {
       for (let i = 0; i < 7; i++) {
-        const tmp = []
+        const tmp = [];
         for (let j = 0; j < 24; j++) {
-          tmp.push(0)
+          tmp.push(0);
         }
-        props.data.push(tmp)
+        props.data.push(tmp);
       }
     }
     for (let i = 0; i < 7; i++) {
       for (let j = 0; j < 24; j++) {
-        items.value[i][j].value = props.data[i][j]
+        items.value[i][j].value = props.data[i][j];
       }
     }
-    bidData.value = props.data
+    bidData.value = props.data;
   },
   { immediate: true }
-)
+);
 
-const handleMouseDown = (rowIndex: number, colIndex: number, event: any) => {
-  if (props.disabled) return
+function handleMouseDown(rowIndex: number, colIndex: number, event: any) {
+  if (props.disabled) return;
   if (event.button === 0) {
-    mouseDown.value = true
-    startRowIndex.value = rowIndex
-    startColIndex.value = colIndex
-    items.value[rowIndex][colIndex].selected = true
-    selectedItems.value[rowIndex].push(colIndex)
+    mouseDown.value = true;
+    startRowIndex.value = rowIndex;
+    startColIndex.value = colIndex;
+    items.value[rowIndex][colIndex].selected = true;
+    selectedItems.value[rowIndex].push(colIndex);
   }
 }
 
-const handleMouseUp = (event: any) => {
-  if (props.disabled) return
+function handleMouseUp(event: any) {
+  if (props.disabled) return;
   if (event.button === 0) {
-    mouseDown.value = false
-    startColIndex.value = 0
-    startRowIndex.value = 0
-    dialogVisible.value = true
+    mouseDown.value = false;
+    startColIndex.value = 0;
+    startRowIndex.value = 0;
+    dialogVisible.value = true;
   }
 }
 
-const handleMouseMove = (rowIndex: number, colIndex: number) => {
-  if (props.disabled) return
+function handleMouseMove(rowIndex: number, colIndex: number) {
+  if (props.disabled) return;
   if (mouseDown.value) {
-    selectCell(rowIndex, colIndex)
+    selectCell(rowIndex, colIndex);
   }
 }
 
-const selectCell = (rowIndex: number, colIndex: number) => {
-  if (props.disabled) return
-  const rowStart = Math.min(startRowIndex.value, rowIndex)
-  const rowEnd = Math.max(startRowIndex.value, rowIndex)
-  const cellStart = Math.min(startColIndex.value, colIndex)
-  const cellEnd = Math.max(startColIndex.value, colIndex)
+function selectCell(rowIndex: number, colIndex: number) {
+  if (props.disabled) return;
+  const rowStart = Math.min(startRowIndex.value, rowIndex);
+  const rowEnd = Math.max(startRowIndex.value, rowIndex);
+  const cellStart = Math.min(startColIndex.value, colIndex);
+  const cellEnd = Math.max(startColIndex.value, colIndex);
 
   for (let i = rowStart; i <= rowEnd; i++) {
     for (let j = cellStart; j <= cellEnd; j++) {
-      items.value[i][j].selected = true
-      selectedItems.value[i].push(j)
+      items.value[i][j].selected = true;
+      selectedItems.value[i].push(j);
     }
   }
 }
 
-const submitBid = () => {
+function submitBid() {
   // console.log(151, 'submitBid', bidData)
   for (const row of Object.keys(selectedItems.value)) {
     for (const col of selectedItems.value[row]) {
-      items.value[row][col].value = bid.value
-      bidData.value[row][col] = bid.value
+      items.value[row][col].value = bid.value;
+      bidData.value[row][col] = bid.value;
     }
   }
-  clearSelectedItems()
-  clearCellBackgroundColor()
-  dialogVisible.value = false
+  clearSelectedItems();
+  clearCellBackgroundColor();
+  dialogVisible.value = false;
 }
 
-const cancelBid = () => {
-  clearSelectedItems()
-  clearCellBackgroundColor()
-  dialogVisible.value = false
+function cancelBid() {
+  clearSelectedItems();
+  clearCellBackgroundColor();
+  dialogVisible.value = false;
 }
 
-const clearCellBackgroundColor = () => {
+function clearCellBackgroundColor() {
   for (const hoursList of items.value) {
     for (const info of hoursList) {
-      info.selected = false
+      info.selected = false;
     }
   }
 }
-const clearSelectedItems = () => {
+
+function clearSelectedItems() {
   for (var i = 0; i < 7; i++) {
-    selectedItems.value[i] = []
+    selectedItems.value[i] = [];
   }
 }
-const resetAllBid = () => {
+
+function resetAllBid() {
   for (let i = 0; i < 7; i++) {
     for (let j = 0; j < 24; j++) {
-      items.value[i][j].value = 0
-      items.value[i][j].selected = false
-      bidData.value[i][j] = 0
+      items.value[i][j].value = 1;
+      items.value[i][j].selected = false;
+      bidData.value[i][j] = 1; // TODO: 不知道是什么东西
     }
   }
 }
-const closeDialog = (done: Function) => {
+
+function closeDialog(done: Function) {
   // console.log("closeDialog")
-  cancelBid()
-  done()
+  cancelBid();
+  done();
 }
 </script>
 
+<template>
+  <div class="calendar">
+    <table class="calendar-table calendar-table-hour">
+      <thead class="calendar-head">
+        <tr>
+          <th rowspan="8" class="week-td">星期 / 时间</th>
+          <th colspan="12">00:00 - 12:00</th>
+          <th colspan="12">12:00 - 24:00</th>
+          <th colspan="4" rowspan="2" class="week-td" style="display: none">小时</th>
+        </tr>
+        <tr>
+          <th colspan="1" v-for="(_, i) in 24" :key="i">{{ i }}</th>
+        </tr>
+      </thead>
+      <tbody>
+        <template v-for="(hoursList, week) in items">
+          <tr>
+            <th>{{ WeekMap[week] }}</th>
+            <td
+              v-for="(info, hour) in hoursList"
+              :key="hour"
+              @mousedown="handleMouseDown(week, hour, $event)"
+              @mouseover="handleMouseMove(week, hour)"
+              @mouseup="handleMouseUp"
+              :style="getTdStyle(info)">
+              <span class="cell-text"> {{ info.value ? info.value : '' }} </span>
+            </td>
+          </tr>
+        </template>
+        <tr>
+          <th colspan="28" class="clear-bar">
+            <span class="middle">可拖动鼠标选择时间段</span>
+            <el-button class="hover-link fr" link @click="resetAllBid" :disabled="disabled">全部重置</el-button>
+          </th>
+        </tr>
+      </tbody>
+    </table>
+
+    <el-dialog v-model="dialogVisible" title="修改出价系数" width="30%" :close-on-click-modal="false" :before-close="closeDialog">
+      <el-input-number v-model="bid" :min="0" :max="100" :step="0.1" controls-position="right" />
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="cancelBid">取消</el-button>
+          <el-button type="primary" @click="submitBid">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
 <style lang="scss" scoped>
 .calendar {
   background-color: #fff;
@@ -280,9 +284,11 @@ table {
     font-size: 13px;
     margin-top: 7px;
   }
+
   .fr {
     float: right;
   }
+
   .middle {
     float: center;
   }

+ 18 - 2
src/components/conditionBuilder/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <div :class="titleClass">条件</div>
+    <div :class="titleClass">
+      <span class="custom-title-icon"></span>
+      条件
+    </div>
     <ConditionGroup
       ref="condiGroupRef"
       :disabled="disabled"
@@ -97,4 +100,17 @@ watch(
 defineExpose({ validate, addConditionGroup })
 </script>
 
-<style scoped></style>
+<style scoped>
+.custom-title-icon {
+  padding-right: 10px;
+}
+
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
+</style>

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

@@ -1,3 +1,89 @@
+<script lang="ts" setup>
+/**
+ * @Name: automation-index.vue
+ * @Description: 自动化初始页
+ * @Author: Cheney
+ */
+
+import { nextTick, onMounted, ref } from 'vue';
+import TimerBid from '/@/views/components/auto/auto-campaigns/timer-bid.vue';
+import TimerBudget from '/@/views/components/auto/auto-campaigns/timer-budget.vue';
+import SwitchCampaign from '/@/views/components/auto/auto-campaigns/switch-campaign.vue';
+import TargetRule from '/@/views/components/auto/auto-campaigns/target-rule.vue';
+import SearchTermRule from '/@/views/components/auto/auto-campaigns/search-term.vue';
+import NegKeywordRule from '/@/views/components/auto/auto-campaigns/neg-keyword.vue';
+import * as api from './api';
+
+interface Props {
+  campaignId: string;
+  profileId: string;
+}
+
+const props = defineProps<Props>();
+const campaignType = 'sp';
+const activeTab = ref(1);
+const RuleStatusButton = ref({
+  '1': '-',
+  '2': '-',
+  '3': '-',
+  '4': '-',
+  '5': '-',
+  '6': '-',
+});
+// const height = ref(800)
+// const campaignRuleStatus = ref({
+//   campaignId: props.campaignId,
+//   campaignType: campaignType,
+//   profileId: props.profileId,
+//   ruleType: activeTab.value,
+//   templateName: '',
+//   useTmpl: false,
+//   template: null,
+//   rule: {
+//     type: activeTab.value,
+//     campaignType: campaignType,
+//     campaignAd: [],
+//     action: {},
+//     activeModel: '',
+//     setTime: '',
+//     weekdays: [],
+//     conditions: [],
+//   },
+//   RuleStatusButton: RuleStatusButton,
+// })
+const baseData = ref({
+  campaignId: props.campaignId,
+  campaignType: campaignType,
+  profileId: props.profileId,
+  ruleType: activeTab,
+});
+
+onMounted(async () => {
+  await initData();
+});
+
+async function initData() {
+  const resp = await api.getCampaignRuleInfo(baseData.value);
+  const info = resp.data[0];
+  if (info) {
+    for (const i of ['1', '2', '3', '4', '5', '6']) {
+      RuleStatusButton.value[i] = info.RuleStatusButton[i];
+    }
+  }
+}
+
+async function changeTab() {
+  // await initData()
+}
+
+async function refresh(tab: number) {
+  activeTab.value = 0;
+  nextTick(() => {
+    activeTab.value = tab;
+  });
+}
+</script>
+
 <template>
   <el-tabs tab-position="left" v-model="activeTab" @tab-change="changeTab">
     <el-tab-pane label="分时调价" :name="1">
@@ -43,7 +129,8 @@
           <span>添加搜索词</span>
         </div>
       </template>
-      <SearchTermRule :data="baseData" :RuleStatusButton="RuleStatusButton" @refresh="refresh(5)" v-if="activeTab === 5"></SearchTermRule>
+      <SearchTermRule :data="baseData" :RuleStatusButton="RuleStatusButton" @refresh="refresh(5)" v-if="activeTab === 5">
+      </SearchTermRule>
     </el-tab-pane>
     <el-tab-pane label="添加否定词" :name="6">
       <template #label>
@@ -52,91 +139,12 @@
           <span>添加否定词</span>
         </div>
       </template>
-      <NegKeywordRule :data="baseData" :RuleStatusButton="RuleStatusButton" @refresh="refresh(6)" v-if="activeTab === 6"></NegKeywordRule>
+      <NegKeywordRule :data="baseData" :RuleStatusButton="RuleStatusButton" @refresh="refresh(6)" v-if="activeTab === 6">
+      </NegKeywordRule>
     </el-tab-pane>
   </el-tabs>
 </template>
 
-<script lang="ts" setup>
-import { ref, Ref, computed, onMounted, nextTick } from 'vue'
-import TimerBid from '/@/views/components/auto/auto-campaigns/timer-bid.vue'
-import TimerBudget from '/@/views/components/auto/auto-campaigns/timer-budget.vue'
-import SwitchCampaign from '/@/views/components/auto/auto-campaigns/switch-campaign.vue'
-import TargetRule from '/@/views/components/auto/auto-campaigns/target-rule.vue'
-import SearchTermRule from '/@/views/components/auto/auto-campaigns/search-term.vue'
-import NegKeywordRule from '/@/views/components/auto/auto-campaigns/neg-keyword.vue'
-import * as api from './api'
-import XEUtils from 'xe-utils'
-
-interface Props {
-  campaignId: string
-  profileId: string
-}
-const props = defineProps<Props>()
-const campaignType = 'sp'
-const activeTab = ref(1)
-const RuleStatusButton = ref({
-  '1': '-',
-  '2': '-',
-  '3': '-',
-  '4': '-',
-  '5': '-',
-  '6': '-',
-})
-// const height = ref(800)
-// const campaignRuleStatus = ref({
-//   campaignId: props.campaignId,
-//   campaignType: campaignType,
-//   profileId: props.profileId,
-//   ruleType: activeTab.value,
-//   templateName: '',
-//   useTmpl: false,
-//   template: null,
-//   rule: {
-//     type: activeTab.value,
-//     campaignType: campaignType,
-//     campaignAd: [],
-//     action: {},
-//     activeModel: '',
-//     setTime: '',
-//     weekdays: [],
-//     conditions: [],
-//   },
-//   RuleStatusButton: RuleStatusButton,
-// })
-const baseData = ref({
-  campaignId: props.campaignId,
-  campaignType: campaignType,
-  profileId: props.profileId,
-  ruleType: activeTab,
-})
-
-const initData = async () => {
-  const resp = await api.getCampaignRuleInfo(baseData.value)
-  const info = resp.data[0]
-  if (info) {
-    for (const i of ['1', '2', '3', '4', '5', '6']) {
-      RuleStatusButton.value[i] = info.RuleStatusButton[i]
-    }
-  }
-}
-
-const changeTab = async () => {
-  // await initData()
-}
-
-const refresh = async (tab: number) => {
-  activeTab.value = 0
-  nextTick(() => {
-    activeTab.value = tab
-  })
-}
-
-onMounted(async () => {
-  await initData()
-})
-</script>
-
 <style lang="scss" scoped>
 .tab-label {
   display: flex;

+ 69 - 53
src/views/components/auto/auto-campaigns/neg-keyword.vue

@@ -1,69 +1,85 @@
-<template>
-  <div>
-    <div class="asj-h2">添加否定词</div>
-    <SelectTmpl :data="formData" />
-    <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef" :disabled="formData.useTmpl">
-      <SearchTermAdd :rule="formData.rule" ref="searchTermAddRef" :disabled="formData.useTmpl"/>
-      <el-form-item prop="rule.action.matchType" required :rules="{ required: true, message: '请至少选择一种匹配方式', trigger: 'change' }">
-        <template #label>
-          <span class="asj-h3">匹配方式</span>
-        </template>
-        <el-checkbox-group v-model="formData.rule.action.matchType">
-          <el-checkbox label="EXECT">精准</el-checkbox>
-          <el-checkbox label="PHRASE">词组</el-checkbox>
-        </el-checkbox-group>
-      </el-form-item>
-    </el-form>
-    <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
-    <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl"/>
-    <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>
-import { ref } from 'vue'
-import SelectTmpl from './select-tmpl.vue'
-import SearchTermAdd from '../search-term-add.vue'
-import FreqSetting from '../freq-setting.vue'
-import conditionBuilder from '/@/components/conditionBuilder/index.vue'
-import XEUtils from 'xe-utils'
-import { userFormData } from './common'
+/**
+ * @Name: neg-keyword.vue
+ * @Description: 自动化-添加否定词
+ * @Author: Cheney
+ */
+import { ref } from 'vue';
+import SelectTmpl from './select-tmpl.vue';
+import SearchTermAdd from '../search-term-add.vue';
+import FreqSetting from '../freq-setting.vue';
+import conditionBuilder from '/@/components/conditionBuilder/index.vue';
+import XEUtils from 'xe-utils';
+import { userFormData } from './common';
 
 interface Props {
   data: {
-    campaignId: string
-    campaignType: string
-    profileId: string
-    ruleType: number
-  }
-  RuleStatusButton?: { [key: string]: any }
+    campaignId: string;
+    campaignType: string;
+    profileId: string;
+    ruleType: number;
+  };
+  RuleStatusButton?: { [key: string]: any };
 }
-const props = defineProps<Props>()
 
-const emits = defineEmits(['refresh'])
-const formRef = ref()
-const searchTermAddRef = ref()
-const condiBuilderRef = ref()
-const { formData, submitFormData } = userFormData(props)
+const props = defineProps<Props>();
+
+const emits = defineEmits(['refresh']);
+const formRef = ref();
+const searchTermAddRef = ref();
+const condiBuilderRef = ref();
+const { formData, submitFormData } = userFormData(props);
 
 const submitForm = async () => {
-  const valid2 = await searchTermAddRef.value.validate()
-  const valid3 = !XEUtils.includes(await condiBuilderRef.value.validate(), false)
+  const valid2 = await searchTermAddRef.value.validate();
+  const valid3 = !XEUtils.includes(await condiBuilderRef.value.validate(), false);
   formRef.value.validate(async (valid: boolean) => {
     if (valid && valid2 && valid3) {
-      await submitFormData()
-      emits('refresh')
+      await submitFormData();
+      emits('refresh');
     } else {
-      console.log('验证失败:', [valid, valid2, valid3])
+      console.log('验证失败:', [valid, valid2, valid3]);
     }
-  })
-}
+  });
+};
 const cancel = () => {
-  emits('refresh')
-}
+  emits('refresh');
+};
 </script>
 
+<template>
+  <div class="mx-5">
+    <div class="asj-h2">添加否定词</div>
+    <SelectTmpl :data="formData" />
+    <el-card class="mt-3">
+      <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
+      <el-divider />
+      <el-form :model="formData" label-position="top" ref="formRef" :disabled="formData.useTmpl">
+        <SearchTermAdd :rule="formData.rule" ref="searchTermAddRef" :disabled="formData.useTmpl" />
+        <el-form-item
+          prop="rule.action.matchType"
+          required
+          :rules="{ required: true, message: '请至少选择一种匹配方式', trigger: 'change' }">
+          <template #label>
+            <span class="asj-h3">匹配方式</span>
+          </template>
+          <el-checkbox-group v-model="formData.rule.action.matchType">
+            <el-checkbox label="EXECT">精准</el-checkbox>
+            <el-checkbox label="PHRASE">词组</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+      </el-form>
+    </el-card>
+    <el-card class="mt-3">
+
+    <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
+    </el-card>
+
+    <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></style>

+ 60 - 48
src/views/components/auto/auto-campaigns/search-term.vue

@@ -1,64 +1,76 @@
-<template>
-  <div>
-    <div class="asj-h2">添加搜索词</div>
-    <SelectTmpl :data="formData" />
-    <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef" :disabled="formData.useTmpl">
-      <SearchTermAdd :rule="formData.rule" :disabled="formData.useTmpl" ref="searchTermAddRef"/>
-      <SearchTermBid :action="formData.rule.action" ref="searchTermBidRef" />
-    </el-form>
-    <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
-    <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
-    <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>
-import { ref, computed } from 'vue'
-import SearchTermAdd from '../search-term-add.vue'
-import SearchTermBid from '../search-term-bid.vue'
-import SelectTmpl from './select-tmpl.vue'
-import FreqSetting from '../freq-setting.vue'
-import conditionBuilder from '/@/components/conditionBuilder/index.vue'
-import { userFormData } from './common'
-import XEUtils from 'xe-utils'
+/**
+ * @Name: search-term
+ * @Description: 自动化-添加搜索词
+ * @Author: Cheney
+ */
+import { ref, computed } from 'vue';
+import SearchTermAdd from '../search-term-add.vue';
+import SearchTermBid from '../search-term-bid.vue';
+import SelectTmpl from './select-tmpl.vue';
+import FreqSetting from '../freq-setting.vue';
+import conditionBuilder from '/@/components/conditionBuilder/index.vue';
+import { userFormData } from './common';
+import XEUtils from 'xe-utils';
 
 interface Props {
   data: {
-    campaignId: string
-    campaignType: string
-    profileId: string
-    ruleType: number
-  }
-  RuleStatusButton?: { [key: string]: any }
+    campaignId: string;
+    campaignType: string;
+    profileId: string;
+    ruleType: number;
+  };
+  RuleStatusButton?: { [key: string]: any };
 }
-const props = defineProps<Props>()
+const props = defineProps<Props>();
+
 
-const emits = defineEmits(['refresh'])
-const formRef = ref()
-const searchTermAddRef = ref()
-const searchTermBidRef = ref()
-const condiBuilderRef = ref()
-const { formData, submitFormData } = userFormData(props)
+const emits = defineEmits(['refresh']);
+const formRef = ref();
+const searchTermAddRef = ref();
+const searchTermBidRef = ref();
+const condiBuilderRef = ref();
+const { formData, submitFormData } = userFormData(props);
 
 const submitForm = async () => {
-  const valid2 = await searchTermAddRef.value.validate()
-  const valid3 = searchTermBidRef.value.valid
-  const valid4 = !XEUtils.includes(await condiBuilderRef.value.validate(), false)
+  const valid2 = await searchTermAddRef.value.validate();
+  const valid3 = searchTermBidRef.value.valid;
+  const valid4 = !XEUtils.includes(await condiBuilderRef.value.validate(), false);
   formRef.value.validate(async (valid: boolean) => {
     if (valid && valid2 && valid3 && valid4) {
-      await submitFormData()
-      emits('refresh')
+      await submitFormData();
+      emits('refresh');
     } else {
-      console.log('验证失败:', [valid, valid2, valid3, valid4])
+      console.log('验证失败:', [valid, valid2, valid3, valid4]);
     }
-  })
-}
+  });
+};
 const cancel = () => {
-  emits('refresh')
-}
+  emits('refresh');
+};
 </script>
 
+<template>
+  <div class="mx-5">
+    <div class="asj-h2">添加搜索词</div>
+    <SelectTmpl :data="formData" />
+    <el-card class="mt-3">
+      <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
+      <el-divider />
+      <el-form :model="formData" label-position="top" ref="formRef" :disabled="formData.useTmpl">
+        <SearchTermAdd :rule="formData.rule" :disabled="formData.useTmpl" ref="searchTermAddRef" />
+        <SearchTermBid :action="formData.rule.action" ref="searchTermBidRef" />
+      </el-form>
+    </el-card>
+    <el-card class="mt-3">
+      <conditionBuilder :data="formData.rule.conditions" ref="condiBuilderRef" :disabled="formData.useTmpl" />
+    </el-card>
+
+    <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></style>

+ 36 - 26
src/views/components/auto/auto-campaigns/select-tmpl.vue

@@ -1,4 +1,10 @@
 <script lang="ts" setup>
+/**
+ * @Name: select-tmpl.vue
+ * @Description: 自动化-选择模板
+ * @Author: Cheney
+ */
+
 import { ref, onMounted, watch } from 'vue';
 import * as api from './api';
 import XEUtils from 'xe-utils';
@@ -10,28 +16,48 @@ interface Props {
 const props = defineProps<Props>();
 const ruleUsage = ref('custom');
 const tmplId = ref(0);
+
 if (props.data.template) {
   tmplId.value = props.data.template.id;
 }
 
 const tmplList = ref([]);
+
 // const emits = defineEmits(['change-tmpl'])
 
-const getTmplList = async () => {
+onMounted(async () => {
+  await getTmplList();
+});
+
+watch(
+    () => props.data.useTmpl,
+    () => {
+      if (props.data.useTmpl) {
+        ruleUsage.value = 'tmpl';
+        tmplId.value = props.data.template.id;
+      } else {
+        ruleUsage.value = 'custom';
+      }
+    }
+);
+
+async function getTmplList() {
   const resp = await api.getTemplateList({
     page: 1,
     pageSize: 999,
     type: props.data.ruleType,
   });
   tmplList.value = resp.data;
-};
-const changeTmpl = () => {
+}
+
+function changeTmpl() {
   const tmpl = XEUtils.find(tmplList.value, (item) => item.id === tmplId.value);
   props.data.rule = XEUtils.clone(tmpl.rule, true);
   props.data.template = tmpl;
   // emits('change-tmpl', tmpl)
-};
-const changeRuleUsage = () => {
+}
+
+function changeRuleUsage() {
   props.data.useTmpl = ruleUsage.value === 'tmpl';
   if (props.data.useTmpl) {
     if (!tmplId.value && tmplList.value.length > 0) {
@@ -39,29 +65,13 @@ const changeRuleUsage = () => {
     }
     changeTmpl();
   }
-};
-
-onMounted(async () => {
-  await getTmplList();
-});
-
-watch(
-  () => props.data.useTmpl,
-  () => {
-    if (props.data.useTmpl) {
-      ruleUsage.value = 'tmpl';
-      tmplId.value = props.data.template.id;
-    } else {
-      ruleUsage.value = 'custom';
-    }
-  }
-);
+}
 
 defineExpose({ getTmplList });
 </script>
 
 <template>
-  <el-card class="mt-2">
+  <el-card class="mt-3">
     <div class="asj-h3">
       <span class="custom-title-icon"></span>
       选择模板
@@ -72,11 +82,11 @@ defineExpose({ getTmplList });
         <el-input
           v-model="data.templateName"
           v-show="ruleUsage === 'custom'"
-          style="margin-left: 22px"
-          placeholder="将规则同时保存为模板(可选)"></el-input>
+          style="margin-left: 22px; width: 220px"
+          placeholder="将规则同时保存为模板(可选)" />
         <el-radio label="tmpl">使用已有模板</el-radio>
         <el-select v-show="ruleUsage === 'tmpl'" @change="changeTmpl" v-model="tmplId" style="margin-left: 22px">
-          <el-option v-for="info in tmplList" :label="info.name" :value="info.id" :key="info.id"></el-option>
+          <el-option v-for="info in tmplList" :label="info.name" :value="info.id" :key="info.id" />
         </el-select>
       </div>
     </el-radio-group>

+ 103 - 75
src/views/components/auto/auto-campaigns/switch-campaign.vue

@@ -1,102 +1,130 @@
-<template>
-  <div>
-    <div class="asj-h2">广告活动</div>
-    <SelectTmpl :data="formData" />
-    <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef" :disabled="formData.useTmpl">
-      <div class="asj-h3">操作</div>
-      <el-checkbox-group v-model="formData.rule.action.state" :disabled="formData.useTmpl">
-        <div>
-          <el-checkbox label="enabled">开始</el-checkbox>
-          <div v-show="formData.rule.action.state.includes('enabled')" style="display: block; margin: 10px 0">
-            <el-form-item
-              prop="rule.action.setTime"
-              :rules="[{ required: formData.rule.action.state.includes('enabled'), message: '必填项', trigger: 'blur' }]">
-              <el-date-picker
-                v-model="formData.rule.action.setTime"
-                type="datetime"
-                format="YYYY-MM-DD HH:mm"
-                time-format="HH:mm"
-                value-format="YYYY-MM-DD HH:mm" />
-            </el-form-item>
-          </div>
-        </div>
-        <el-checkbox label="paused" @click="addConditions">暂停</el-checkbox>
-      </el-checkbox-group>
-    </el-form>
-    <div v-show="formData.rule.action.state.includes('paused')">
-      <conditionBuilder :data="formData.rule.conditions" :candidate-fields="condidateFields" ref="condiBuilderRef" :disabled="formData.useTmpl" />
-    </div>
-
-    <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl"/>
-
-    <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>
-import { ref, onMounted, Ref } from 'vue'
-import { WeekMap } from '../enum'
-import SelectTmpl from './select-tmpl.vue'
-import conditionBuilder from '/@/components/conditionBuilder/index.vue'
-import { userFormData } from './common'
-import FreqSetting from '../freq-setting.vue'
-import XEUtils from 'xe-utils'
+/**
+ * @Name: switch-campaign
+ * @Description: 自动化-广告活动
+ * @Author: Cheney
+ */
+
+import { ref } 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';
 
 interface Props {
   data: {
-    campaignId: string
-    campaignType: string
-    profileId: string
-    ruleType: number
-  }
-  RuleStatusButton?: { [key: string]: any }
+    campaignId: string;
+    campaignType: string;
+    profileId: string;
+    ruleType: number;
+  };
+  RuleStatusButton?: { [key: string]: any };
 }
-const props = defineProps<Props>()
-const emits = defineEmits(['refresh'])
-const formRef = ref()
-const condiBuilderRef = ref()
-const { formData, submitFormData } = userFormData(props)
-formData.value.rule.action.state = []
-formData.value.rule.action.setTime = ''
+
+const props = defineProps<Props>();
+const emits = defineEmits(['refresh']);
+const formRef = ref();
+const condiBuilderRef = ref();
+const { formData, submitFormData } = userFormData(props);
+formData.value.rule.action.state = [];
+formData.value.rule.action.setTime = '';
 
 const condidateFields = [
   { label: '曝光量', value: 'impressions' },
   { label: '点击量', value: 'clicks' },
   { label: '转化率', value: 'cr', suffix: '%' },
   { label: '单次点击费用', value: 'cpc', prefix: '$' },
-]
-const addConditions = () => {
+];
+
+function addConditions() {
   if (formData.value.rule.conditions.length === 0) {
-    condiBuilderRef.value.addConditionGroup()
+    condiBuilderRef.value.addConditionGroup();
   }
 }
 
-const validateConditionsForm = async () => {
-  const validList = await condiBuilderRef.value.validate()
+async function validateConditionsForm() {
+  const validList = await condiBuilderRef.value.validate();
   if (validList.includes(false)) {
-    return false
+    return false;
   }
-  return true
+  return true;
 }
-const submitForm = async () => {
-  const valid2 = await validateConditionsForm()
+
+async function submitForm() {
+  const valid2 = await validateConditionsForm();
   formRef.value.validate(async (valid: any) => {
     if (valid && valid2) {
-      await submitFormData()
-      emits('refresh')
+      await submitFormData();
+      emits('refresh');
     } else {
-      console.log('验证失败')
+      console.log('验证失败');
     }
-  })
+  });
 }
 
-const cancel = () => {
-  emits('refresh')
+function cancel() {
+  emits('refresh');
 }
-
 </script>
 
-<style scoped></style>
+<template>
+  <div class="mx-5">
+    <div class="asj-h2">广告活动</div>
+    <SelectTmpl :data="formData" />
+    <el-card class="mt-3">
+      <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
+      <el-divider />
+      <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef" :disabled="formData.useTmpl">
+        <div class="asj-h3">
+          <span class="custom-title-icon"></span>
+          操作
+        </div>
+        <el-checkbox-group v-model="formData.rule.action.state" :disabled="formData.useTmpl">
+          <div>
+            <el-checkbox label="enabled">开始</el-checkbox>
+            <div v-show="formData.rule.action.state.includes('enabled')" style="display: block; margin: 10px 0">
+              <el-form-item
+                prop="rule.action.setTime"
+                :rules="[{ required: formData.rule.action.state.includes('enabled'), message: '必填项', trigger: 'blur' }]">
+                <el-date-picker
+                  v-model="formData.rule.action.setTime"
+                  type="datetime"
+                  format="YYYY-MM-DD HH:mm"
+                  time-format="HH:mm"
+                  value-format="YYYY-MM-DD HH:mm" />
+              </el-form-item>
+            </div>
+          </div>
+          <el-checkbox label="paused" @click="addConditions">暂停</el-checkbox>
+        </el-checkbox-group>
+      </el-form>
+      <div v-show="formData.rule.action.state.includes('paused')">
+        <conditionBuilder
+          :data="formData.rule.conditions"
+          :candidate-fields="condidateFields"
+          ref="condiBuilderRef"
+          :disabled="formData.useTmpl" />
+      </div>
+    </el-card>
+
+    <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-title-icon {
+  padding-right: 10px;
+}
+
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
+</style>

+ 29 - 12
src/views/components/auto/auto-campaigns/target-rule.vue

@@ -1,4 +1,10 @@
 <script lang="ts" setup>
+/**
+ * @Name: target-rule
+ * @Description: 自动化-定向规则
+ * @Author: Cheney
+ */
+
 import { ref, computed } from 'vue';
 import TargetSelect from '../target-select.vue';
 import SelectTmpl from './select-tmpl.vue';
@@ -40,20 +46,31 @@ const cancel = () => {
 </script>
 
 <template>
-  <div>
+  <div class="mx-5">
     <div class="asj-h2">定向规则</div>
     <SelectTmpl :data="formData" />
-    <el-form class="custom-card" :model="formData" label-position="top" style="margin-top: 20px" ref="formRef">
-      <el-form-item>
-        <!--<template #label>-->
-        <!--  <span class="custom-title-icon"></span>-->
-        <!--  <span class="asj-h3">生效对象</span>-->
-        <!--</template>-->
-        <TargetSelect mode="auto" :data="formData.rule" :useTmpl="formData.useTmpl" :campaign-id="data.campaignId"></TargetSelect>
-      </el-form-item>
-    </el-form>
-    <TargetRuleSetting :rule="formData.rule" ref="ruleSettingRef" :disabled="formData.useTmpl" />
-    <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
+    <el-card class="mt-3">
+      <el-form class="custom-card" :model="formData" label-position="top" ref="formRef">
+        <el-form-item>
+          <!--<template #label>-->
+          <!--  <span class="custom-title-icon"></span>-->
+          <!--  <span class="asj-h3">生效对象</span>-->
+          <!--</template>-->
+          <FreqSetting :rule="formData.rule" :disabled="formData.useTmpl" />
+          <el-divider />
+          <TargetSelect
+            mode="auto"
+            :data="formData.rule"
+            :useTmpl="formData.useTmpl"
+            :campaign-id="data.campaignId"></TargetSelect>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card class="mt-3">
+      <TargetRuleSetting :rule="formData.rule" ref="ruleSettingRef" :disabled="formData.useTmpl" />
+    </el-card>
+
     <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>

+ 38 - 30
src/views/components/auto/auto-campaigns/timer-bid.vue

@@ -1,10 +1,14 @@
 <script lang="ts" setup>
-import { ref, onMounted, Ref, watch, reactive } from 'vue';
+/**
+ * @Name: timer-bid.vue
+ * @Description: 自动化-分时调价组件
+ * @Author: Cheney
+ */
+
+import { ref } from 'vue';
 import TimerBidTable from '/@/components/TimerBidTable/index.vue';
 import SelectTmpl from './select-tmpl.vue';
 import { userFormData } from './common';
-import XEUtils from 'xe-utils';
-import dayjs from 'dayjs';
 
 interface Props {
   data: {
@@ -22,30 +26,6 @@ const emits = defineEmits(['refresh']);
 const formRef = ref();
 const { formData, submitFormData } = userFormData(props);
 
-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 submit = async () => {
-  formRef.value.validate(async (valid: any) => {
-    if (valid) {
-      await submitFormData();
-      emits('refresh');
-    } else {
-      console.log('验证失败');
-    }
-  });
-};
-const cancel = async () => {
-  emits('refresh');
-};
-
 const timeRange = ref('Option1');
 const schedule = ref('Option1');
 const bid = ref('');
@@ -79,6 +59,7 @@ const timeRangeOptions = [
     label: '深夜: 21:00-23:59',
   },
 ];
+
 const scheduleOptions = [
   {
     value: 'Option1',
@@ -93,10 +74,36 @@ const scheduleOptions = [
     label: '仅在周末',
   },
 ];
+const isVisible = ref(true);
+
+function 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('请先设置竞价!'));
+}
+
+async function submit() {
+  formRef.value.validate(async (valid: any) => {
+    if (valid) {
+      await submitFormData();
+      emits('refresh');
+    } else {
+      console.log('验证失败');
+    }
+  });
+}
+
+function cancel() {
+  emits('refresh');
+}
 
-const isVisible = ref(true)
 function handleClose() {
-  isVisible.value = false
+  isVisible.value = false;
 }
 </script>
 
@@ -111,7 +118,8 @@ function handleClose() {
             <div class="tag-content">
               <strong>自动化分时规则:</strong>
               <p>
-                1. 应用分时调价后,如需手动修改竞价,只能在Xmars操作。在亚马逊后台或其他第三方系统进行的调价操作,竞价将会被Xmars当前时段的自动化执行结果覆盖。
+                1.
+                应用分时调价后,如需手动修改竞价,只能在Xmars操作。在亚马逊后台或其他第三方系统进行的调价操作,竞价将会被Xmars当前时段的自动化执行结果覆盖。
               </p>
               <p>2. 广告活动开启分时调价,规则的修改将在下一个整点生效。</p>
             </div>

+ 72 - 47
src/views/components/auto/auto-campaigns/timer-budget.vue

@@ -1,69 +1,94 @@
-<template>
-  <div>
-    <div class="asj-h2">分时预算</div>
-    <SelectTmpl :data="formData" />
-    <el-form :model="formData" label-position="top" style="margin-top: 20px" ref="formRef">
-      <el-form-item prop="rule.conditions" :rules="[{ validator: checkConditions, trigger: 'xxx' }]">
-        <template #label>
-          <span class="asj-h3">设置预算</span>
-        </template>
-        <TimerBudgetTable :data="formData.rule.conditions" @click="formRef.clearValidate('rule.conditions')" :disabled="formData.useTmpl" />
-      </el-form-item>
-    </el-form>
-
-    <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>
-import { ref, onMounted, watch, Ref } from 'vue'
-import SelectTmpl from './select-tmpl.vue'
-import TimerBudgetTable from '/@/components/TimerBudgetTable/index.vue'
-import XEUtils from 'xe-utils'
-import { userFormData } from './common'
+/**
+ * @Name: timer-budget.vue
+ * @Description: 自动化-分时预算
+ * @Author: Cheney
+ */
+
+import { ref } from 'vue';
+import SelectTmpl from './select-tmpl.vue';
+import TimerBudgetTable from '/@/components/TimerBudgetTable/index.vue';
+import XEUtils from 'xe-utils';
+import { userFormData } from './common';
 
 interface Props {
   data: {
-    campaignId: string
-    campaignType: string
-    profileId: string
-    ruleType: number
-  }
-  RuleStatusButton?: { [key: string]: any }
+    campaignId: string;
+    campaignType: string;
+    profileId: string;
+    ruleType: number;
+  };
+  RuleStatusButton?: { [key: string]: any };
 }
-const props = defineProps<Props>()
-const emits = defineEmits(['refresh'])
-const formRef = ref()
-const { formData, submitFormData } = userFormData(props)
 
-const checkConditions = (rule: any, value: any, callback: any) => {
+const props = defineProps<Props>();
+const emits = defineEmits(['refresh']);
+const formRef = ref();
+const { formData, submitFormData } = userFormData(props);
+
+function checkConditions(rule: any, value: any, callback: any) {
   for (const bidList of formData.value.rule.conditions) {
     for (const info of bidList) {
       if (info.value && XEUtils.toNumber(info.value) > 0) {
-        return callback()
+        return callback();
       }
     }
   }
-  callback(new Error('请先设置预算!'))
+  callback(new Error('请先设置预算!'));
 }
 
-const submitForm = async () => {
+async function submitForm() {
   formRef.value.validate(async (valid: any) => {
     if (valid) {
-      await submitFormData()
-      emits('refresh')
+      await submitFormData();
+      emits('refresh');
     } else {
-      console.log('验证失败')
+      console.log('验证失败');
     }
-  })
-}
-const cancel = () => {
-  emits('refresh')
+  });
 }
 
+function cancel() {
+  emits('refresh');
+}
 </script>
 
-<style scoped></style>
+<template>
+  <div class="mx-5">
+    <div class="asj-h2">分时预算</div>
+    <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' }]">
+          <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" />
+        </el-form-item>
+      </el-form>
+    </el-card>
+    <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-title-icon {
+  padding-right: 10px;
+}
+
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
+</style>

+ 34 - 11
src/views/components/auto/freq-setting.vue

@@ -1,19 +1,42 @@
+<script lang="ts" setup>
+/**
+ * @Name: freq-setting.vue
+ * @Description: 自动化-广告活动-频率设置
+ * @Author: Cheney
+ */
+
+import { ref } from 'vue';
+import { WeekMap } from './enum';
+
+interface Props {
+  rule: AutoRule;
+  disabled?: boolean;
+}
+
+const props = defineProps<Props>();
+</script>
+
 <template>
-  <div class="asj-h3">频率设置</div>
+  <div class="asj-h3">
+    <span class="custom-title-icon"></span>
+    频率设置
+  </div>
   <el-checkbox-group v-model="rule.weekdays" :disabled="disabled">
     <el-checkbox v-for="i in [1, 2, 3, 4, 5, 6, 0]" :label="i" :key="i">{{ WeekMap[i] }}</el-checkbox>
   </el-checkbox-group>
 </template>
 
-<script lang="ts" setup>
-import { ref } from 'vue'
-import { WeekMap } from './enum'
-
-interface Props {
-  rule: AutoRule,
-  disabled?: boolean
+<style scoped>
+.custom-title-icon {
+  padding-right: 10px;
 }
-const props = defineProps<Props>()
-</script>
 
-<style scoped></style>
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
+</style>

+ 52 - 28
src/views/components/auto/search-term-add.vue

@@ -1,7 +1,13 @@
 <template>
   <div>
-    <div class="asj-h3">添加到</div>
-    <el-radio-group style="display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 20px" v-model="rule.activeModel">
+    <div class="asj-h3">
+      <span class="custom-title-icon"></span>
+
+      添加到
+    </div>
+    <el-radio-group
+      style="display: flex; flex-direction: column; align-items: flex-start; margin-bottom: 20px"
+      v-model="rule.activeModel">
       <el-radio label="all">当前广告活动(所有广告组)</el-radio>
       <el-radio label="current">当前广告活动(当前广告组)</el-radio>
       <el-radio label="specified">指定广告活动的广告组</el-radio>
@@ -28,7 +34,12 @@
                 width="450px"
                 :query="{ profileId: '3006125408623189', campaignType: info.campaignType, campaignId: info.campaignId }">
               </AdGroupSelect>
-              <el-button link :icon="Delete" type="danger" @click="delCampaignAd(index)" v-show="rule.campaignAd.length > 1"></el-button>
+              <el-button
+                link
+                :icon="Delete"
+                type="danger"
+                @click="delCampaignAd(index)"
+                v-show="rule.campaignAd.length > 1"></el-button>
             </div>
           </el-form-item>
         </el-form>
@@ -39,50 +50,63 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue'
-import { Plus, Delete } from '@element-plus/icons-vue'
-import CampaignSelect from '/@/views/components/campaign-select/index.vue'
-import AdGroupSelect from '/@/views/components/ad-group-select/index.vue'
+import { ref, onMounted } from 'vue';
+import { Plus, Delete } from '@element-plus/icons-vue';
+import CampaignSelect from '/@/views/components/campaign-select/index.vue';
+import AdGroupSelect from '/@/views/components/ad-group-select/index.vue';
 
 interface Props {
-  rule: AutoRule
-  disabled?: boolean
+  rule: AutoRule;
+  disabled?: boolean;
 }
 
-const props = defineProps<Props>()
-const formRef = ref()
+const props = defineProps<Props>();
+const formRef = ref();
 
 const addCampaignAd = () => {
   props.rule.campaignAd.push({
     campaignType: 'sp',
     campaignId: '',
     adGroupId: '',
-  })
-}
+  });
+};
 if (props.rule.campaignAd.length === 0) {
-  addCampaignAd()
+  addCampaignAd();
 }
 if (!props.rule.activeModel) {
-  props.rule.activeModel = 'all'
+  props.rule.activeModel = 'all';
 }
 const delCampaignAd = (index: number) => {
-  props.rule.campaignAd.splice(index, 1)
-}
+  props.rule.campaignAd.splice(index, 1);
+};
 
 const changeCampaignType = async (info: any) => {
-  info.campaignId = ''
-  info.adGroupId = ''
-}
+  info.campaignId = '';
+  info.adGroupId = '';
+};
 
 const validate = async () => {
-  let ret = true
-  if(!formRef.value) return ret
+  let ret = true;
+  if (!formRef.value) return ret;
   await formRef.value.validate(async (valid: boolean) => {
-    ret = valid
-  })
-  return ret
-}
-defineExpose({ validate })
+    ret = valid;
+  });
+  return ret;
+};
+defineExpose({ validate });
 </script>
 
-<style scoped></style>
+<style scoped>
+.custom-title-icon {
+  padding-right: 10px;
+}
+
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
+</style>

+ 17 - 1
src/views/components/auto/search-term-bid.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <div class="asj-h3">设置竞价</div>
+    <div class="asj-h3">
+      <span class="custom-title-icon"></span>
+      设置竞价
+    </div>
     <div>关键词竞价:</div>
     <div v-for="(info, index) in action.keywords" class="bid">
       <el-checkbox v-model="info.use" :label="info.matchType">{{ MatchTypeMap[info.matchType] }}</el-checkbox>
@@ -63,4 +66,17 @@ defineExpose({ valid })
 .target {
   gap: 49px;
 }
+
+.custom-title-icon {
+  padding-right: 10px;
+}
+
+.custom-title-icon:before {
+  content: '';
+  width: 4px;
+  height: 15px;
+  background: #3569d6;
+  position: absolute;
+  transform: translateY(25%);
+}
 </style>

+ 160 - 134
src/views/components/auto/target-rule-setting.vue

@@ -1,87 +1,24 @@
-<template>
-  <el-card>
-    <div class="asj-h3">
-      <span class="custom-title-icon"></span>
-      规则设置
-    </div>
-    <div class="rule-setting-container">
-      <div v-for="(info, index) in rule.conditions" class="rule-setting-item" :key="info.actionType + info.ordering">
-        <div class="rule-setting-title">
-          <div class="asj-h3">{{ RuleNameMap[info.actionType] }} - 规则{{ info.ordering }}</div>
-          <div class="rule-setting-opration">
-            <el-tooltip content="添加规则" placement="top" effect="dark" v-if="!disabled">
-              <el-icon @click="addRule(info.actionType, index)" :disabled="disabled"><Plus /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="删除规则" placement="top" effect="dark" v-if="rule.conditions.length !== 1 && !disabled">
-              <el-icon @click="delRule(index)"><DeleteFilled /></el-icon>
-            </el-tooltip>
-            <el-tooltip content="收起/展开规则" placement="top" effect="dark">
-              <el-icon @click="info.show = !info.show"><ArrowDown /></el-icon>
-            </el-tooltip>
-          </div>
-        </div>
-        <div v-show="info.show">
-          <el-form :inline="true" :model="info.action" ref="ruleFormRef" :disabled="disabled">
-            <div v-if="info.actionType === 'increase' || info.actionType === 'decrease'">
-              <el-form-item>
-                <el-select v-model="actionModel" @change="changeAction(info.action)" style="width: 250px">
-                  <el-option
-                    v-for="item in ActionList"
-                    :key="item.value"
-                    :value="item.value"
-                    :label="item.label.replace('_', info.actionType === 'increase' ? '升高' : '降低')">
-                  </el-option>
-                </el-select>
-              </el-form-item>
-              <el-form-item prop="set" :rules="{ validator: checkFloat }">
-                <InputFloat
-                  v-model="info.action.set"
-                  :prefix="info.action.numType === 'num' ? '$' : ''"
-                  :suffix="info.action.numType === 'ratio' ? '%' : ''"
-                  style="width: 120px">
-                </InputFloat>
-              </el-form-item>
-              <el-form-item label="最大值" v-if="info.actionType === 'increase'" prop="max" :rules="{ validator: checkMinMax }">
-                <InputFloat v-model="info.action.max" style="width: 120px" prefix="$"></InputFloat>
-              </el-form-item>
-              <el-form-item label="最小值" v-else prop="min" :rules="{ validator: checkMinMax }">
-                <InputFloat v-model="info.action.min" style="width: 120px" prefix="$"></InputFloat>
-              </el-form-item>
-            </div>
-            <el-form-item v-if="info.actionType === 'set'" prop="set">
-              <el-input v-model="info.action.set"></el-input>
-            </el-form-item>
-          </el-form>
-          <conditionBuilder :data="info.conditions" :candidate-fields="candidateFields" ref="condiBuilderRef" :disabled="disabled" />
-        </div>
-      </div>
-      <el-popover placement="bottom-start" trigger="hover" :disabled="disabled">
-        <template #reference>
-          <el-button :icon="Plus" type="warning" :disabled="disabled">添加规则</el-button>
-        </template>
-        <div class="popver-content">
-          <span class="popver-content-item" v-for="[name, title] of Object.entries(RuleNameMap)" @click="addRule(name)">{{ title }}</span>
-        </div>
-      </el-popover>
-    </div>
-  </el-card>
-</template>
-
 <script lang="ts" setup>
-import { ref, computed } from 'vue'
-import { Plus } from '@element-plus/icons-vue'
-import conditionBuilder from '/@/components/conditionBuilder/index.vue'
-import InputFloat from '/@/components/input-float/index.vue'
-import { useSymbolOptions } from '/@/components/conditionBuilder/utils'
-import { ActionList, RuleNameMap } from './enum'
+/**
+ * @Name: target-rule-setting
+ * @Description: 自动化-规则定向-规则设置
+ * @Author: Cheney
+ */
 
-import XEUtils from 'xe-utils'
+import { ref, computed } from 'vue';
+import { Plus } from '@element-plus/icons-vue';
+import conditionBuilder from '/@/components/conditionBuilder/index.vue';
+import InputFloat from '/@/components/input-float/index.vue';
+import { useSymbolOptions } from '/@/components/conditionBuilder/utils';
+import { ActionList, RuleNameMap } from './enum';
+import XEUtils from 'xe-utils';
 
 interface Props {
-  rule: AutoRule
-  disabled?: boolean
+  rule: AutoRule;
+  disabled?: boolean;
 }
-const props = defineProps<Props>()
+
+const props = defineProps<Props>();
 const candidateFields = [
   { label: '曝光量', value: 'impressions' },
   { label: '点击量', value: 'clicks' },
@@ -104,40 +41,46 @@ const candidateFields = [
       { label: '商品-单个商品', value: 'asin' },
     ],
   },
-  { label: '关键词名称', value: 'keywords', type: 'array' }
-]
-const { getSymbolOptions } = useSymbolOptions(candidateFields)
-const ruleFormRef = ref()
-const condiBuilderRef = ref()
-const checkFloat = (rule: any, value: any, callback: any) => {
-  if (XEUtils.toNumber(value) <= 0) {
-    callback(new Error('请输入大于0的数值!'))
-  } else {
-    callback()
-  }
-}
-const checkMinMax = (rule: any, value: any, callback: any) => {
-  if (XEUtils.toNumber(value) <= 0) {
-    callback(new Error('请输入大于0的数值!'))
-  } else {
-    callback()
-  }
-}
+  { label: '关键词名称', value: 'keywords', type: 'array' },
+];
+
+const { getSymbolOptions } = useSymbolOptions(candidateFields);
+const ruleFormRef = ref();
+const condiBuilderRef = ref();
+const actionModel = ref('bid-ratio');
+
 const maxRuleNumber = computed(() => {
   const ret = {
     increase: 0,
     decrease: 0,
     set: 0,
     pause: 0,
-  }
+  };
   for (const info of props.rule.conditions) {
-    ret[info.actionType] = Math.max(ret[info.actionType] || 0, info.ordering)
+    ret[info.actionType] = Math.max(ret[info.actionType] || 0, info.ordering);
+  }
+  return ret;
+});
+
+function checkFloat(rule: any, value: any, callback: any) {
+  if (XEUtils.toNumber(value) <= 0) {
+    callback(new Error('请输入大于0的数值!'));
+  } else {
+    callback();
+  }
+}
+
+function checkMinMax(rule: any, value: any, callback: any) {
+  if (XEUtils.toNumber(value) <= 0) {
+    callback(new Error('请输入大于0的数值!'));
+  } else {
+    callback();
   }
-  return ret
-})
-const addRule = (actionType: string, index: number = -1) => {
-  const field = candidateFields[0].value
-  const symbol = getSymbolOptions(field)[0].value
+}
+
+function addRule(actionType: string, index: number = -1) {
+  const field = candidateFields[0].value;
+  const symbol = getSymbolOptions(field)[0].value;
   const rule = {
     action: { set: '', baseType: '', max: '', min: '', numType: 'ratio' },
     actionType: actionType,
@@ -162,62 +105,145 @@ const addRule = (actionType: string, index: number = -1) => {
     exceptDay: '',
     ordering: maxRuleNumber.value[actionType] + 1,
     show: true,
-  }
+  };
   if (index === -1) {
-    props.rule.conditions.push(rule)
+    props.rule.conditions.push(rule);
   } else {
-    props.rule.conditions.splice(index + 1, 0, rule)
+    props.rule.conditions.splice(index + 1, 0, rule);
   }
 }
+
 if (props.rule.conditions.length === 0) {
-  props.rule.campaignType = 'sp'
-  props.rule.activeModel = 'campaign'
-  addRule('increase', -1)
+  props.rule.campaignType = 'sp';
+  props.rule.activeModel = 'campaign';
+  addRule('increase', -1);
 }
 
-const delRule = (index: number) => {
-  props.rule.conditions.splice(index, 1)
+function delRule(index: number) {
+  props.rule.conditions.splice(index, 1);
 }
 
-const actionModel = ref('bid-ratio')
-const changeAction = (action: any) => {
-  const [a, b] = actionModel.value.split('-')
-  action.baseType = a
-  action.numType = b
-  action.set = ''
+function changeAction(action: any) {
+  const [a, b] = actionModel.value.split('-');
+  action.baseType = a;
+  action.numType = b;
+  action.set = '';
 }
 
-const validateConditionsForm = async () => {
-  const ret = []
+async function validateConditionsForm() {
+  const ret = [];
   for (const f of condiBuilderRef.value) {
-    ret.push(await f.validate())
+    ret.push(await f.validate());
   }
   for (const validList of ret) {
     if (validList.includes(false)) {
-      return false
+      return false;
     }
   }
-  return true
+  return true;
 }
-const validateRuleForm = async () => {
-  const ret = []
+
+async function validateRuleForm() {
+  const ret = [];
   for (const f of ruleFormRef.value) {
     await f.validate((valid: boolean) => {
-      ret.push(valid)
-    })
+      ret.push(valid);
+    });
   }
-  return !ret.includes(false)
+  return !ret.includes(false);
 }
 
-const validateForm = async () => {
-  const valid1 = await validateRuleForm()
-  const valid2 = await validateConditionsForm()
-  return valid1 && valid2
+async function validateForm() {
+  const valid1 = await validateRuleForm();
+  const valid2 = await validateConditionsForm();
+  return valid1 && valid2;
 }
 
-defineExpose({ validateForm })
+defineExpose({ validateForm });
 </script>
 
+<template>
+  <div>
+    <div class="asj-h3">
+      <span class="custom-title-icon"></span>
+      规则设置
+    </div>
+    <div class="rule-setting-container">
+      <div v-for="(info, index) in rule.conditions" class="rule-setting-item" :key="info.actionType + info.ordering">
+        <div class="rule-setting-title">
+          <div class="asj-h3">{{ RuleNameMap[info.actionType] }} - 规则{{ info.ordering }}</div>
+          <div class="rule-setting-opration">
+            <el-tooltip content="添加规则" placement="top" effect="dark" v-if="!disabled">
+              <el-icon @click="addRule(info.actionType, index)" :disabled="disabled">
+                <Plus />
+              </el-icon>
+            </el-tooltip>
+            <el-tooltip content="删除规则" placement="top" effect="dark" v-if="rule.conditions.length !== 1 && !disabled">
+              <el-icon @click="delRule(index)">
+                <DeleteFilled />
+              </el-icon>
+            </el-tooltip>
+            <el-tooltip content="收起/展开规则" placement="top" effect="dark">
+              <el-icon @click="info.show = !info.show">
+                <ArrowDown />
+              </el-icon>
+            </el-tooltip>
+          </div>
+        </div>
+        <div v-show="info.show">
+          <el-form :inline="true" :model="info.action" ref="ruleFormRef" :disabled="disabled">
+            <!-- 竞价规则下面的下拉框和两个输入框 -->
+            <div v-if="info.actionType === 'increase' || info.actionType === 'decrease'">
+              <el-form-item>
+                <el-select v-model="actionModel" @change="changeAction(info.action)" style="width: 250px">
+                  <el-option
+                    v-for="item in ActionList"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label.replace('_', info.actionType === 'increase' ? '升高' : '降低')">
+                  </el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item prop="set" :rules="{ validator: checkFloat }">
+                <InputFloat
+                  v-model="info.action.set"
+                  :prefix="info.action.numType === 'num' ? '$' : ''"
+                  :suffix="info.action.numType === 'ratio' ? '%' : ''"
+                  style="width: 120px">
+                </InputFloat>
+              </el-form-item>
+              <el-form-item label="最大值" v-if="info.actionType === 'increase'" prop="max" :rules="{ validator: checkMinMax }">
+                <InputFloat v-model="info.action.max" style="width: 120px" prefix="$"></InputFloat>
+              </el-form-item>
+              <el-form-item label="最小值" v-else prop="min" :rules="{ validator: checkMinMax }">
+                <InputFloat v-model="info.action.min" style="width: 120px" prefix="$"></InputFloat>
+              </el-form-item>
+            </div>
+            <el-form-item v-if="info.actionType === 'set'" prop="set">
+              <el-input v-model="info.action.set"></el-input>
+            </el-form-item>
+          </el-form>
+          <conditionBuilder
+            :data="info.conditions"
+            :candidate-fields="candidateFields"
+            ref="condiBuilderRef"
+            :disabled="disabled" />
+        </div>
+      </div>
+      <el-popover placement="bottom-start" trigger="hover" :disabled="disabled">
+        <template #reference>
+          <el-button :icon="Plus" type="warning" :disabled="disabled">添加规则</el-button>
+        </template>
+        <div class="popver-content">
+          <span class="popver-content-item" v-for="[name, title] of Object.entries(RuleNameMap)" @click="addRule(name)">{{
+            title
+          }}</span>
+        </div>
+      </el-popover>
+    </div>
+  </div>
+</template>
+
 <style lang="scss" scoped>
 .popver-content {
   display: flex;

+ 2 - 2
src/views/components/auto/target-select.vue

@@ -12,7 +12,7 @@ const props = defineProps<Props>()
 </script>
 
 <template>
-  <el-card body-style="width: 100%;">
+  <div>
     <span class="custom-title-icon"></span>
     <span class="asj-h3">生效对象</span>
     <div style="display: flex; flex-direction: column; align-items: flex-start">
@@ -44,7 +44,7 @@ const props = defineProps<Props>()
         </div>
       </el-radio-group>
     </div>
-  </el-card>
+  </div>
 </template>
 
 <style lang="scss" scoped>

+ 3 - 0
src/views/demo/index.vue

@@ -1,3 +1,6 @@
+/**
+ * @Description: 测试
+ */
 <script setup lang="ts">
 import * as ww from '@wecom/jssdk';
 import { WeComLogin } from './api';