Преглед изворни кода

✨ feat:

自定义定向--购买再营销已完成; 修改表格布局; 修复el-select选择后map()失效的问题; 修复页面加载内存泄露卡顿的问题, 减少自定义定向下搜索tab的树形结构数据请求频率;
WanGxC пре 1 година
родитељ
комит
2bab1f2d8f

+ 4 - 4
src/views/adManage/sb/campaigns/CreateCampaigns/component/KeywordTarget.vue

@@ -49,7 +49,7 @@
             <div class="card-header">
               <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedKeyWordsTableData.length }}</span>
               <span style="color: #529b2e">成功: {{ successCount }}</span>
-              <span style="color: #c45656">失败: {{ errorCount }}</span>
+              <span style="color: #c45656">失败: {{ failureCount }}</span>
               <el-button class="button" type="danger" text bg @click="delAllKeyWords">全部删除</el-button>
             </div>
           </template>
@@ -137,7 +137,7 @@ const MATCH_TYPE_MAP = {
   精确: 'exact',
 }
 const successCount = ref('')
-const errorCount = ref('')
+const failureCount = ref('')
 
 function addKeyWords() {
   const trimmedText = keyWordsTextarea.value.trim()
@@ -199,7 +199,7 @@ function delAllKeyWords() {
 async function keyWordsSave() {
   keywordsLoading.value = true
   successCount.value = ''
-  errorCount.value = ''
+  failureCount.value = ''
 
   const keywordList = addedKeyWordsTableData.value.map((kw) => ({
     keywordText: kw.keyword,
@@ -226,7 +226,7 @@ async function keyWordsSave() {
         type: 'success',
       })
       successCount.value = resp.data.success.length
-      errorCount.value = resp.data.error.length
+      failureCount.value = resp.data.error.length
       delAllKeyWords()
     } else {
       ElMessage.error('关键词创建失败!')

+ 2 - 2
src/views/adManage/sd/campaigns/CreateCampaigns/api/index.ts

@@ -101,10 +101,10 @@ export function getAudiencesList(query) {
       params: query,
   })
 }
-export function post(body) {
+export function postCustomOperation(body) {
   return request({
       url: '/api/ad_manage/sdtargets/create/',
-      method: 'get',
+      method: 'post',
       data: body,
   })
 }

+ 1 - 1
src/views/adManage/sd/campaigns/CreateCampaigns/component/AdCampaign.vue

@@ -93,7 +93,7 @@ interface campaignRuleForm {
   budget: string
 }
 const campaignRuleForm = reactive<campaignRuleForm>({
-  campaignName: 'AiTestW01',
+  campaignName: 'AiTestX2024_06',
   adMix: '',
   startDate: '',
   endDate: '',

+ 2 - 2
src/views/adManage/sd/campaigns/CreateCampaigns/component/AdGroup.vue

@@ -69,7 +69,7 @@ interface ruleForm {
   budget: string
 }
 const ruleForm = reactive<ruleForm>({
-  groupName: 'AiTestW01',
+  groupName: 'AiTestX2024_01',
   adMix: '',
   startDate: '',
   endDate: '',
@@ -96,7 +96,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
 }
 
 // 竞价优化按钮组
-const bidOptimization = ref('reach')
+const bidOptimization = ref('clicks')
 
 // 创建广告组
 const adGroupLoading = ref(false)

+ 4 - 4
src/views/adManage/sd/campaigns/CreateCampaigns/component/BrowseSearch.vue

@@ -145,10 +145,10 @@ async function setProductOrientationData() {
 
 function dialogSelectChange(event) {
   // 使用 map 来转换每个选中项的 value 为其对应的 label
-  selectedLabels.value = event.map((selectedValue) => {
-    const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
-    return selectedOption ? selectedOption.label : ''
-  })
+  // selectedLabels.value = event.map((selectedValue) => {
+  //   const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
+  //   return selectedOption ? selectedOption.label : ''
+  // })
 }
 
 function resetDialogForm() {

+ 13 - 9
src/views/adManage/sd/campaigns/CreateCampaigns/component/BuySearch.vue

@@ -24,7 +24,7 @@
     <el-form :model="dialogForm" :rules="dialogRules" ref="dialogFormRef" style="margin-top: 20px">
       <el-form-item style="padding-left: 140px">
         <span style="margin-right: 10px; color: #616266; font-weight: 500">品牌</span>
-        <el-select v-model="dialogForm.dialogselectValue" @change="dialogSelectChange" multiple placeholder="请选择" :loading="dialogSelectLoading">
+        <el-select v-model="dialogForm.dialogselectValue" @change="dialogSelectChange" placeholder="请选择" :loading="dialogSelectLoading">
           <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </el-form-item>
@@ -145,10 +145,11 @@ async function setProductOrientationData() {
 
 function dialogSelectChange(event) {
   // 使用 map 来转换每个选中项的 value 为其对应的 label
-  selectedLabels.value = event.map((selectedValue) => {
-    const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
-    return selectedOption ? selectedOption.label : ''
-  })
+  // console.log('event', event)
+  // selectedLabels.value = event.map((selectedValue) => {
+  //   const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
+  //   return selectedOption ? selectedOption.label : ''
+  // })
 }
 
 function resetDialogForm() {
@@ -236,7 +237,7 @@ function refine(data) {
   setDialogOption()
 }
 
-const emit = defineEmits(['add-to-table','form-submitted'])
+const emit = defineEmits(['add-to-table', 'form-submitted'])
 
 // 定向按钮功能
 function orientate(node, data) {
@@ -270,8 +271,7 @@ async function dialogFormSubmit() {
   }
 
   // 使用emit发送封装好的数据到父组件
-  emit('form-submitted', formData);
-  // emitter.emit('form-submitted', formData)
+  emit('form-submitted', formData)
 
   // 关闭弹窗
   visible.value = false
@@ -279,7 +279,11 @@ async function dialogFormSubmit() {
 }
 
 onMounted(() => {
-  setProductOrientationData()
+  emitter.on('tree-node-data', () => {
+    if (searchClassifyTableData.value.length == 0) {
+      setProductOrientationData()
+    }
+  })
 })
 </script>
 

+ 227 - 117
src/views/adManage/sd/campaigns/CreateCampaigns/component/CustomTarget.vue

@@ -5,7 +5,7 @@
         <span class="custom-card-icon">|</span>
         <span class="custom-card-Text">自定义定向</span>
       </div>
-      <div class="main-container">
+      <div class="main-container" v-loading="loading">
         <div class="left-container">
           <el-tabs v-model="topTabName" class="demo-tabs" @tab-click="handleClick">
             <div class="tab-container-fixed-top">
@@ -63,7 +63,7 @@
                     <hr style="margin: 5px 0" />
                     <div>
                       <div style="display: flex; justify-content: space-between">
-                        <div>推广商品</div>
+                        <div>推广商品</div>
                         <el-button type="primary" size="small" link @click="addPromoteProduct">添加</el-button>
                       </div>
                       <div style="display: flex; justify-content: space-between">
@@ -88,7 +88,20 @@
                   </el-select>
                 </div>
                 <el-tab-pane label="建议" name="advice">
-                  <div style="height: 450px"></div>
+                  <div style="height: 450px">
+                    <div>动态细分</div>
+                    <hr style="margin: 5px 0" />
+                    <div>
+                      <div style="display: flex; justify-content: space-between">
+                        <div>推广的商品</div>
+                        <el-button type="primary" size="small" link @click="addPromoteProduct">添加</el-button>
+                      </div>
+                      <div style="display: flex; justify-content: space-between">
+                        <div>与推广的商品相关</div>
+                        <el-button type="primary" size="small" link @click="addPromoteSimilar">添加</el-button>
+                      </div>
+                    </div>
+                  </div>
                 </el-tab-pane>
                 <el-tab-pane label="搜索" name="search">
                   <BuySearch @add-to-table="buyOrientation" @form-submitted="buyRefine"></BuySearch>
@@ -100,16 +113,21 @@
         <!-- 右侧内容区 -->
         <div class="right-container">
           <div class="right-container-top">
-            <div style="padding-left: 15px; font-size: 15px">
+            <!-- <div style="padding-left: 15px; font-size: 15px">
               <span style="color: #8e9095">已添加: </span> <span style="font-weight: 500">{{ addedTableData.length }}</span>
-            </div>
+            </div> -->
+            <el-text class="mx-1" style="padding-left: 15px">已添加: {{ addedTableData.length }}</el-text>
+            <el-text class="mx-1" type="success">成功: {{ successCount }}</el-text>
+            <el-text class="mx-1" type="danger">失败: {{ failureCount }}</el-text>
+
+            <!-- <span style="color: #529b2e">成功: {{ successCount }}</span> -->
+            <!-- <span style="color: #c45656">失败: {{ failureCount }}</span> -->
             <el-button link type="danger" @click="handleDeleteAll" style="margin-right: 15px">删除所有</el-button>
           </div>
           <el-table :data="addedTableData" :header-cell-style="changeTableHeader" height="600" style="width: 100%">
             <el-table-column prop="date" label="商品">
               <template #default="{ row }">
-                <!-- <div>{{ row }}</div> -->
-                <div v-if="row.currentTitle">{{ row.currentTitle }}</div>
+                <div v-if="row.currentTitle" style="color: black; font-weight: 450">{{ row.currentTitle }}</div>
                 <div v-if="row.productType">
                   商品类型: <span style="color: black">{{ productTypeDict[row.productType] }}</span>
                 </div>
@@ -117,12 +135,13 @@
                   分类: <span style="color: black">{{ row.cna ? row.cna : row.dialogTitle }}</span>
                 </div>
                 <div v-if="row.low_prices || row.high_prices">
-                  商品价格: <span style="color: black">{{ row.low_prices ? row.low_prices : '--' }}</span>
-                  <span style="color: black">{{ row.high_prices ? row.high_prices : '--' }}</span>
+                  商品价格: <span style="color: black">${{ row.low_prices ? row.low_prices : '--' }}</span>
+                  <span> -</span>
+                  <span style="color: black"> ${{ row.high_prices ? row.high_prices : '--' }}</span>
                 </div>
                 <div v-if="row.delivery">
                   配送:
-                  <span style="color: black">{{ row.delivery ? row.delivery : '--' }}</span>
+                  <span style="color: black">{{ row.delivery ? deliveryMap[row.delivery] : '--' }}</span>
                 </div>
                 <div v-if="row.lookback">
                   回溯期: <span style="color: black">{{ row.lookback ? row.lookback : '--' }}</span>
@@ -165,16 +184,19 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, reactive, CSSProperties, onUnmounted } from 'vue'
+import { onMounted, ref, watch, Ref, inject, reactive, CSSProperties, onUnmounted } from 'vue'
 import { request } from '/@/utils/service'
 import BrowseSearch from './BrowseSearch.vue'
 import BuySearch from './BuySearch.vue'
 import { ElMessage, type FormInstance, type FormRules, type TabsPaneContext } from 'element-plus'
-import { getAudiencesList } from '../api/index'
+import { getAudiencesList, postCustomOperation } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { storeToRefs } from 'pinia'
 import emitter from '/@/utils/emitter'
 
+
+const respCampaignId = inject<Ref>('respCampaignId')
+const respAdGroupId = ref('')
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
 
@@ -182,7 +204,11 @@ const { profile } = storeToRefs(shopInfo)
 const topTabName = ref('audience')
 
 function handleClick(tab: TabsPaneContext, event: Event) {
-  // console.log(tab, event)
+  console.log(tab.props.label)
+  if (tab.props.label == '购买再营销') {
+    console.log(111)
+    emitter.emit('tree-node-data')
+  }
 }
 
 // tab栏顶部固定部分功能
@@ -294,8 +320,9 @@ const purchasesLookBackOptions = [
 const addedTableData = ref([])
 
 function browseOrientation(data) {
-  const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
-  if (!exists) {
+
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
+  // if (!exists) {
     const dataWithLookback = {
       currentTitle: '浏览再营销',
       type: 'c',
@@ -306,29 +333,33 @@ function browseOrientation(data) {
       classificationId: data.cid,
     }
     addedTableData.value.push(dataWithLookback)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 function buyOrientation(data) {
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
-  if (!exists) {
+  // const exists = addedTableData.value.some((item) => item.cid === data.cid)
+  // if (!exists) {
     const dataWithLookback = {
-      ...data,
-      lookback: viewsLookBack.value,
       currentTitle: '购买再营销',
+      type: 'c',
+      tactictype: 'purchases',
+      cna: data.cna,
+      lookback: viewsLookBack.value,
+      bid: bid.value,
+      classificationId: data.cid,
     }
     addedTableData.value.push(dataWithLookback)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 // 点击细化后弹窗确认触发
@@ -339,9 +370,9 @@ const deliveryMap = {
 }
 
 function browseRefine(data) {
-  const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
 
-  if (!exists) {
+  // if (!exists) {
     const tableRow = {
       currentTitle: '浏览再营销',
       type: 'c',
@@ -351,7 +382,6 @@ function browseRefine(data) {
       brandId: data.selectedBrands,
       classificationId: data.cid,
       delivery: data.delivery,
-      // categoryId: data.categoryId,
       dialogTitle: data.dialogTitle, // 分类名称
       low_prices: data.prices.lowest,
       high_prices: data.prices.highest,
@@ -359,42 +389,39 @@ function browseRefine(data) {
       high_rating: data.starRating[1],
     }
     addedTableData.value.push(tableRow) // 添加到表格数据
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 function buyRefine(data) {
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
-
-  if (!exists) {
-    // addedTableData.value.push(data)
-    const deliveryText = deliveryMap[data.delivery]
-
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
+  // if (!exists) {
     const tableRow = {
       currentTitle: '购买再营销',
       type: 'c',
-      tactictype: 'views',
+      tactictype: 'purchases',
       lookback: viewsLookBack.value,
       bid: bid.value,
-      brandId: data.brandId,
+      brandId: data.selectedBrands,
       classificationId: data.cid,
       delivery: data.delivery,
-      categoryId: data.categoryId,
       dialogTitle: data.dialogTitle, // 分类名称
-      prices: `\$${data.prices.lowest} ~ \$${data.prices.highest}`, // 价格范围
-      // 其他需要的字段...
+      low_prices: data.prices.lowest,
+      high_prices: data.prices.highest,
+      low_rating: data.starRating[0],
+      high_rating: data.starRating[1],
     }
     addedTableData.value.push(tableRow) // 添加到表格数据
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 // 获取搜索的数据
@@ -432,18 +459,15 @@ async function getCustomData() {
 }
 
 function handleAddButtonClick(row) {
-  // 检查该行数据是否已经存在于 addedTableData 中
-  const exists = addedTableData.value.some((item) => item.audienceId === row.audienceId)
-  if (!exists) {
-    // 如果不存在,则添加它到 addedTableData
-    addedTableData.value.push(row)
-  } else {
-    // 如果已存在,显示一个警告消息
-    ElMessage({
-      message: `选项 ${row.audienceName} 已经添加,不能重复添加`,
-      type: 'warning',
-    })
-  }
+  // const exists = addedTableData.value.some((item) => item.audienceId === row.audienceId)
+  // if (!exists) {
+  //   addedTableData.value.push(row)
+  // } else {
+  //   ElMessage({
+  //     message: `选项 ${row.audienceName} 已经添加,不能重复添加`,
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 // 删除所有Table数据
@@ -464,68 +488,152 @@ function singleDelete(row) {
   } else if ('classificationId' in row) {
     addedTableData.value = addedTableData.value.filter((item) => item.classificationId !== row.classificationId)
   } else if ('asin' in row) {
+    // 浏览再营销的建议项添加
     addedTableData.value = addedTableData.value.filter((item) => !(item.productType === row.productType && item.asin === row.asin))
   }
 }
 
 const promoteGoodsAsin = ref('')
 const productTypeDict = {
-  relatedProduct: '推广商品',
-  similarProduct: '浏览再营销',
+  exactProduct: '推广商品',
+  relatedProduct: '与推广的商品相关',
+  similarProduct: '与推广商品类似的商品',
 }
 
 function addPromoteProduct() {
-  const newProduct = {
-    currentTitle: '浏览再营销',
-    type: 'p',
-    tactictype: 'view',
-    lookback: viewsLookBack.value,
-    asin: promoteGoodsAsin.value,
-    bid: 0.2,
-    productType: 'relatedProduct',
-  }
-  const exists = addedTableData.value.some(
-    (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
-  )
-
-  if (!exists) {
-    addedTableData.value.push(newProduct)
-  } else {
-    ElMessage({
-      message: '已经添加了相关商品,不能重复添加',
-      type: 'warning',
-    })
+  // 通过tab判断
+  if (topTabName.value == 'views') {
+    const newProduct = {
+      currentTitle: '浏览再营销',
+      type: 'p',
+      tactictype: 'views',
+      lookback: viewsLookBack.value,
+      asin: promoteGoodsAsin.value,
+      bid: 0.2,
+      productType: 'exactProduct',
+    }
+    const exists = addedTableData.value.some(
+      (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
+    )
+
+    if (!exists) {
+      addedTableData.value.push(newProduct)
+    } else {
+      ElMessage({
+        message: '已经添加了相关商品,不能重复添加',
+        type: 'warning',
+      })
+    }
+  } else if (topTabName.value == 'purchases') {
+    const newProduct = {
+      currentTitle: '购买再营销',
+      type: 'p',
+      tactictype: 'purchases',
+      lookback: viewsLookBack.value,
+      asin: promoteGoodsAsin.value,
+      bid: 0.2,
+      productType: 'exactProduct',
+    }
+    const exists = addedTableData.value.some(
+      (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
+    )
+
+    if (!exists) {
+      addedTableData.value.push(newProduct)
+    } else {
+      ElMessage({
+        message: '已经添加了相关商品,不能重复添加',
+        type: 'warning',
+      })
+    }
   }
 }
 
 function addPromoteSimilar() {
-  const newProduct = {
-    currentTitle: '浏览再营销',
-    type: 'p',
-    tactictype: 'view',
-    lookback: viewsLookBack.value,
-    asin: promoteGoodsAsin.value,
-    bid: 0.2,
-    productType: 'similarProduct',
-  }
-  const exists = addedTableData.value.some(
-    (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
-  )
-
-  if (!exists) {
-    addedTableData.value.push(newProduct)
-  } else {
-    ElMessage({
-      message: '已经添加了相关商品,不能重复添加',
-      type: 'warning',
-    })
+  if (topTabName.value == 'views') {
+    const newProduct = {
+      currentTitle: '浏览再营销',
+      type: 'p',
+      tactictype: 'views',
+      lookback: viewsLookBack.value,
+      asin: promoteGoodsAsin.value,
+      bid: 0.2,
+      productType: 'similarProduct',
+    }
+    const exists = addedTableData.value.some(
+      (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
+    )
+
+    if (!exists) {
+      addedTableData.value.push(newProduct)
+    } else {
+      ElMessage({
+        message: '已经添加了相关商品,不能重复添加',
+        type: 'warning',
+      })
+    }
+  } else if (topTabName.value == 'purchases') {
+    const newProduct = {
+      currentTitle: '购买再营销',
+      type: 'p',
+      tactictype: 'purchases',
+      lookback: viewsLookBack.value,
+      asin: promoteGoodsAsin.value,
+      bid: 0.2,
+      productType: 'relatedProduct',
+    }
+    const exists = addedTableData.value.some(
+      (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
+    )
+
+    if (!exists) {
+      addedTableData.value.push(newProduct)
+    } else {
+      ElMessage({
+        message: '已经添加了相关商品,不能重复添加',
+        type: 'warning',
+      })
+    }
   }
 }
 
-// 点击保存触发
-function handleSave() {
-  // createCustom()
-  console.log('addedTableData:', addedTableData.value)
+// TODO: 测试state的值是paused
+const loading = ref(false)
+const successCount = ref('')
+const failureCount = ref('')
+
+async function handleSave() {
+  const body = {
+    profile_id: profile.value.profile_id,
+    campaignId: respCampaignId.value,
+    adGroupId: respAdGroupId.value,
+    tactic: 'T00030',
+    expressionType: 'manual',
+    expressionList: addedTableData.value,
+    state: 'paused',
+  }
+  try {
+    loading.value = true
+    const response = await postCustomOperation(body)
+    successCount.value = response.data.success.length
+    failureCount.value = response.data.error.length
+    if (response.data.error.length == 0) {
+      addedTableData.value = []
+      ElMessage({
+        message: '创建成功',
+        type: 'success',
+      })
+    } else {
+      ElMessage({
+        message: '创建失败',
+        type: 'error',
+      })
+    }
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    loading.value = false
+  }
 }
 
 function changeTableHeader(row) {
@@ -539,12 +647,14 @@ function changeTableHeader(row) {
 onMounted(() => {
   emitter.on('send-firstAsin', (value: any) => {
     promoteGoodsAsin.value = value
-    console.log('promoteGoodsAsin', promoteGoodsAsin.value)
+  })
+  emitter.on('respAdGroupId', (value: any) => {
+    respAdGroupId.value = value
   })
 })
 
 onUnmounted(() => {
-  emitter.off('send-firstAsin')
+  emitter.all.clear()
 })
 </script>
 

+ 1 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/PromoteProduct.vue

@@ -343,6 +343,7 @@ async function createAds() {
     productLoading.value = false
     if (resp.data[0].adId) {
       addedTableData.value = []
+      addedAdsTableItems.value = []
       ElMessage({
         message: '商品创建成功',
         type: 'success',