Переглянути джерело

Merge branch 'wang' into test

WanGxC 1 рік тому
батько
коміт
6a51a4d86d

+ 0 - 2
src/views/adManage/sb/campaigns/CreateCampaigns/component/AdCampaign.vue

@@ -286,8 +286,6 @@ async function createCampaigns() {
     console.error('请求失败:', error)
   } finally {
     campaignLoading.value = false
-    console.log('prevCampaignId.value', prevCampaignId.value)
-    console.log('respCampaignId.value', respCampaignId.value)
   }
 }
 

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

@@ -219,7 +219,6 @@ async function getPageOptions() {
   try {
     const response = await getStoreurl({ profile_id: profile.value.profile_id })
     pageOptions.value = response.data.storePageInfo
-    // console.log('pageOptions:', pageOptions.value)
   } catch (error) {
     console.log('error:', error)
   }
@@ -229,11 +228,8 @@ let selectedPage: any = ''
 watch(
   () => ruleForm.page,
   (newPageValue) => {
-    // Find the selected page from pageOptions
     selectedPage = pageOptions.value.find((page) => page.storePageUrl === newPageValue)
     if (selectedPage) {
-      // Print the storePageUrl of the selected page
-      // console.log(`Selected page URL: ${selectedPage.storePageUrl}`)
       setTimeout(() => {
         emitter.emit('page', selectedPage.storePageUrl)
       }, 2000)

+ 4 - 7
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('关键词创建失败!')
@@ -293,7 +293,4 @@ div {
 .data-color {
   color: rgb(30, 33, 41);
 }
-// ::v-deep(div#pane-first) {
-//   margin-left: 15px;
-// }
 </style>

+ 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,
   })
 }

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

@@ -56,7 +56,7 @@
           </div>
         </div>
         <el-form-item label="投放类型">
-          <el-radio-group v-model="targetType">
+          <el-radio-group v-model="targetType" @change="changeTargetType">
             <el-radio label="T00030">受众</el-radio>
             <el-radio label="T00020">内容相关投放</el-radio>
           </el-radio-group>
@@ -93,7 +93,7 @@ interface campaignRuleForm {
   budget: string
 }
 const campaignRuleForm = reactive<campaignRuleForm>({
-  campaignName: 'AiTestW01',
+  campaignName: 'AiTestX2024_19',
   adMix: '',
   startDate: '',
   endDate: '',
@@ -111,7 +111,6 @@ const submitCampaignForm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
     if (valid) {
-      console.log('submit!')
       createCampaigns()
     } else {
       console.log('error submit!', fields)
@@ -173,7 +172,7 @@ async function createCampaigns() {
 }
 
 // 提供数据给父组件
-const emit = defineEmits(['send-campaign'])
+const emit = defineEmits(['send-campaign', 'send-targetType'])
 
 watch([respCampaignId, respCampaignName], () => {
   if (respCampaignId.value && respCampaignName.value) {
@@ -184,9 +183,13 @@ watch([respCampaignId, respCampaignName], () => {
   }
 })
 
-watch(targetType, () => {
-  emitter.emit('send-targetType', targetType)
-},{immediate: true})
+// function changeTargetType() {
+//   emitter.emit('send-targetType', targetType)
+// }
+function changeTargetType(value: string) {
+  targetType.value = value // 更新 targetType
+  emit('send-targetType', { type: targetType.value }) // 发送新的 targetType 值给父组件
+}
 
 onMounted(() => {
   buildAdMix()

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

@@ -47,12 +47,12 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, reactive, ref, watch, defineEmits, inject, Ref } from 'vue'
 import type { FormInstance, FormRules } from 'element-plus'
 import { ElMessage } from 'element-plus'
-import PromoteProduct from './PromoteProduct.vue'
-import { postAdGroup } from '../api/index'
 import { storeToRefs } from 'pinia'
+import { Ref, inject, reactive, ref, watch } from 'vue'
+import { postAdGroup } from '../api/index'
+import PromoteProduct from './PromoteProduct.vue'
 import { useShopInfo } from '/@/stores/shopInfo'
 import emitter from '/@/utils/emitter'
 
@@ -69,7 +69,7 @@ interface ruleForm {
   budget: string
 }
 const ruleForm = reactive<ruleForm>({
-  groupName: 'AiTestW01',
+  groupName: 'AiTestX2024',
   adMix: '',
   startDate: '',
   endDate: '',
@@ -87,16 +87,15 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
     if (valid) {
-      console.log('submit!')
       crateAdGroup()
     } else {
-      console.log('error submit!', fields)
+      console.error('error submit!', fields)
     }
   })
 }
 
 // 竞价优化按钮组
-const bidOptimization = ref('reach')
+const bidOptimization = ref('clicks')
 
 // 创建广告组
 const adGroupLoading = ref(false)
@@ -104,8 +103,7 @@ const respAdGroupId = ref('')
 
 async function crateAdGroup() {
   adGroupLoading.value = true
-  try {
-    const body = {
+  const body = {
       profile_id: profile.value.profile_id,
       campaignId: respCampaignId.value,
       defaultBid: 0.1,
@@ -113,6 +111,7 @@ async function crateAdGroup() {
       state: 'paused',
       creativeType: 'IMAGE',
     }
+  try {
     const response = await postAdGroup(body)
     if (response.data.adGroupId) {
       respAdGroupId.value = response.data.adGroupId
@@ -127,9 +126,12 @@ async function crateAdGroup() {
   }
 }
 
+const emit = defineEmits(['send-groupId'])
+
 watch(
   () => respAdGroupId.value,
   () => {
+    emit('send-groupId', { adGroupId: respAdGroupId.value })
     setTimeout(() => {
       emitter.emit('respAdGroupId', respAdGroupId.value)
     }, 2000)

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

@@ -7,7 +7,7 @@
             <span style="width: 75%">{{ node.label }}</span>
             <span style="color: rgb(50, 108, 216)" v-if="data.ta == true">
               <a @click="refine(data)"> 细化 </a>
-              <a style="margin-left: 8px" @click="orientate(node, data)"> 定向 </a>
+              <a style="margin-left: 8px" @click="orientate(node, data)"> 添加 </a>
             </span>
           </span>
         </template>
@@ -144,11 +144,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 : ''
-  })
+  // 转换选中项的 value 为其对应的 label
+  const selectedOption = dialogForm.dialogOptions.find((option) => option.value === event)
+  if (selectedOption) {
+    selectedLabels.value = selectedOption.label
+  }
 }
 
 function resetDialogForm() {
@@ -270,8 +270,7 @@ async function dialogFormSubmit() {
   }
 
   // 使用emit发送封装好的数据到父组件
-  emit('form-submitted', formData);
-  // emitter.emit('form-submitted', formData)
+  emit('form-submitted', formData)
 
   // 关闭弹窗
   visible.value = false

+ 17 - 15
src/views/adManage/sd/campaigns/CreateCampaigns/component/BuySearch.vue

@@ -7,7 +7,7 @@
             <span style="width: 75%">{{ node.label }}</span>
             <span style="color: rgb(50, 108, 216)" v-if="data.ta == true">
               <a @click="refine(data)"> 细化 </a>
-              <a style="margin-left: 8px" @click="orientate(node, data)"> 定向 </a>
+              <a style="margin-left: 8px" @click="orientate(node, data)"> 添加 </a>
             </span>
           </span>
         </template>
@@ -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>
@@ -62,12 +62,11 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, reactive, CSSProperties, defineEmits } from 'vue'
-import { request } from '/@/utils/service'
-import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
-import { useShopInfo } from '/@/stores/shopInfo'
 import { storeToRefs } from 'pinia'
+import { CSSProperties, defineEmits, onMounted, reactive, ref } from 'vue'
+import { useShopInfo } from '/@/stores/shopInfo'
 import emitter from '/@/utils/emitter'
+import { request } from '/@/utils/service'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -144,11 +143,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 : ''
-  })
+  // 转换选中的 value 为其对应的 label
+  const selectedOption = dialogForm.dialogOptions.find((option) => option.value === event)
+  if (selectedOption) {
+    selectedLabels.value = selectedOption.label
+  }
 }
 
 function resetDialogForm() {
@@ -236,7 +235,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 +269,7 @@ async function dialogFormSubmit() {
   }
 
   // 使用emit发送封装好的数据到父组件
-  emit('form-submitted', formData);
-  // emitter.emit('form-submitted', formData)
+  emit('form-submitted', formData)
 
   // 关闭弹窗
   visible.value = false
@@ -279,7 +277,11 @@ async function dialogFormSubmit() {
 }
 
 onMounted(() => {
-  setProductOrientationData()
+  emitter.on('tree-node-data', () => {
+    if (searchClassifyTableData.value.length == 0) {
+      setProductOrientationData()
+    }
+  })
 })
 </script>
 

+ 26 - 16
src/views/adManage/sd/campaigns/CreateCampaigns/component/CategorySearch.vue

@@ -1,13 +1,13 @@
 <template>
   <div v-loading="containerLoading">
-    <el-scrollbar height="450px">
+    <el-scrollbar height="525px">
       <el-tree :data="searchClassifyTableData" :props="defaultProps" :expand-on-click-node="false">
         <template #default="{ node, data }">
           <span class="custom-tree-node">
             <span style="width: 75%">{{ node.label }}</span>
             <span style="color: rgb(50, 108, 216)" v-if="data.ta == true">
               <a @click="refine(data)"> 细化 </a>
-              <a style="margin-left: 8px" @click="orientate(node, data)"> 定向 </a>
+              <a style="margin-left: 8px" @click="orientate(node, data)"> 添加 </a>
             </span>
           </span>
         </template>
@@ -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>
@@ -62,18 +62,18 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, reactive, CSSProperties, defineEmits } from 'vue'
-import { request } from '/@/utils/service'
-import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
-import { useShopInfo } from '/@/stores/shopInfo'
+import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
+import { CSSProperties, defineEmits, onMounted, reactive, ref } from 'vue'
+import { useShopInfo } from '/@/stores/shopInfo'
 import emitter from '/@/utils/emitter'
+import { request } from '/@/utils/service'
+
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
 
 const containerLoading = ref(false)
-const categoryBiddingType = ref('customBid')
 const countLoadig = ref(false)
 const dialogSelectLoading = ref(false)
 const searchClassifyTableData = ref([])
@@ -87,7 +87,7 @@ let dialogTitle = ref('')
 let categoryId = ref('')
 let commodityCount = ref([])
 let currentDialogIndex = ref(0)
-let selectedLabels = ref([]) // 选中的label数组
+let selectedLabels = ref('') // 选中的label数组
 
 interface Mark {
   style: CSSProperties
@@ -138,17 +138,23 @@ async function setProductOrientationData() {
     searchClassifyTableData.value = resp.data
   } catch (error) {
     console.error('请求失败:', error)
+    ElMessage({
+        message: error.message,
+        type: 'warning',
+      })
   } finally {
     containerLoading.value = false
   }
 }
 
 function dialogSelectChange(event) {
-  // 使用 map 来转换每个选中项的 value 为其对应的 label
-  selectedLabels.value = event.map((selectedValue) => {
-    const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
-    return selectedOption ? selectedOption.label : ''
-  })
+  // 转换选中项的 value 为其对应的 label
+  const selectedOption = dialogForm.dialogOptions.find((option) => option.value === event)
+  if (selectedOption) {
+    selectedLabels.value = selectedOption.label
+  } else {
+    selectedLabels.value = ''; // 如果没有找到(即没有选中项),则设置为空字符串
+  }
 }
 
 function resetDialogForm() {
@@ -236,7 +242,7 @@ function refine(data) {
   setDialogOption()
 }
 
-const emit = defineEmits(['add-to-table','form-submitted'])
+const emit = defineEmits(['add-to-table','form-submitted', 'remove-from-table'])
 
 // 定向按钮功能
 function orientate(node, data) {
@@ -279,7 +285,11 @@ async function dialogFormSubmit() {
 }
 
 onMounted(() => {
-  setProductOrientationData()
+  emitter.on('get-category-tree-data', () => {
+    if (searchClassifyTableData.value.length == 0) {
+      setProductOrientationData()
+    }
+  })
 })
 </script>
 

+ 247 - 181
src/views/adManage/sd/campaigns/CreateCampaigns/component/ContentTarget.vue

@@ -5,9 +5,9 @@
         <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">
+          <el-tabs v-model="topTabName" class="demo-tabs">
             <div class="tab-container-fixed-top">
               <span class="tab-top-label">添加定向时的竞价设置: </span>
               <el-select v-model="bidType">
@@ -18,39 +18,34 @@
               </el-input>
             </div>
             <el-tab-pane label="品类" name="category">
-              <!-- <div class="tab-title">触达浏览过您推广的商品或其他类似商品、商品品类、品牌以及其他商品功能的顾客</div> -->
-              <el-tabs v-model="viewsTabName" type="border-card">
-                <!-- <div class="el-row align-center-bottom">
-                  <span class="select-label">回溯期: </span>
-                  <el-select v-model="viewsLookBack">
-                    <el-option v-for="item in viewsLookBackOptions" :key="item.value" :label="item.label" :value="item.value" />
-                  </el-select>
-                </div> -->
+              <el-tabs v-model="viewsTabName" type="border-card" @tab-click="handleTabClick">
                 <el-tab-pane label="建议" name="advice">
-                  <div style="height: 490px"></div>
+                  <div style="height: 525px">
+                    <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="addDynamicSegment">添加</el-button>
+                      </div>
+                    </div>
+                  </div>
                 </el-tab-pane>
                 <el-tab-pane label="搜索" name="search">
-                  <CategorySearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></CategorySearch>
+                  <CategorySearch @add-to-table="handleAddToTable" @form-submitted="handleRefine"></CategorySearch>
                 </el-tab-pane>
               </el-tabs>
             </el-tab-pane>
             <el-tab-pane label="单个商品" name="single">
-              <!-- <div class="tab-title">向购买过广告商品或其他相关商品、商品类别、品牌及其他商品功能的购买者传递信息</div> -->
               <el-tabs v-model="purchasesTabName" type="border-card">
-                <!-- <div class="el-row align-center-bottom">
-                  <span class="select-label">回溯期: </span>
-                  <el-select v-model="purchasesLookBack">
-                    <el-option v-for="item in purchasesLookBackOptions" :key="item.value" :label="item.label" :value="item.value" />
-                  </el-select>
-                </div> -->
                 <el-tab-pane label="建议" name="advice">
-                  <div style="height: 490px"></div>
+                  <div style="height: 525px"></div>
                 </el-tab-pane>
                 <el-tab-pane label="搜索" name="search">
                   <SingleSearch @updateSelected="handleSelectedCommodities"></SingleSearch>
                 </el-tab-pane>
                 <el-tab-pane label="输入" name="input">
-                  <div style="height: 490px"></div>
+                  <div style="height: 525px"></div>
                 </el-tab-pane>
               </el-tabs>
             </el-tab-pane>
@@ -59,42 +54,52 @@
         <!-- 右侧内容区 -->
         <div class="right-container">
           <div class="right-container-top">
-            <div style="padding-left: 15px; font-size: 15px">
-              <span style="color: #8e9095">已添加: </span> <span style="font-weight: 500">{{ addedTableData.length }}</span>
-            </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>
             <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 v-if="row.cna || row.dialogTitle">浏览再营销</div>
+                <div v-if="row.currentTitle" style="color: black; font-weight: 450">{{ row.currentTitle }}</div>
                 <div v-if="row.cna || row.dialogTitle">
                   分类: <span style="color: black">{{ row.cna ? row.cna : row.dialogTitle }}</span>
                 </div>
-                <div v-if="row.prices">
-                  商品价格: <span style="color: black">{{ row.prices ? row.prices : '--' }}</span>
+                <div v-if="row.brand">
+                  品牌: <span style="color: black">{{ row.brand ? row.brand : '--' }}</span>
+                </div>
+                <div v-if="row.low_prices || row.high_prices">
+                  商品价格: <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 === 'eligible' ? '具备Prime资格' : '不具备Prime资格') : '--' }}</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>
+                <div v-if="row.lookback">
+                  回溯期: <span style="color: black">{{ row.lookback ? row.lookback : '--' }}</span>
                 </div>
-                <div v-if="row.image_link && row.asin">
-                  <div style="display: flex; align-items: center">
-                    <div style="margin-right: 8px; line-height: normal">
-                      <el-image class="img-box" :src="row.image_link" />
-                    </div>
-                    <div>
-                      <el-tooltip class="box-item" effect="dark" :content="row.title" placement="top">
-                        <div class="double-line">{{ row.title ? row.title : '--' }}</div>
-                      </el-tooltip>
-                      <span
-                        >ASIN:
-                        <span class="data-color" style="margin-right: 8px">{{ row.asin ? row.asin : '--' }}</span>
-                      </span>
-                    </div>
+                <div v-if="row.audienceName">
+                  <div style="font-weight: 500; color: #000">{{ row.category }}</div>
+                  <el-tooltip class="box-item" effect="dark" :content="row.audienceName" placement="top">
+                    <div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden">{{ row.audienceName }}</div>
+                  </el-tooltip>
+                  <div style="color: rgba(0, 0, 0, 0.45)">预估人数: {{ row.lowerBoundInclusive }} - {{ row.upperBoundExclusive }}</div>
+                </div>
+                <div v-if="row.image_link" style="display: flex; align-items: center">
+                  <div style="margin-right: 8px; line-height: normal">
+                    <el-image class="img-box" :src="row.image_link" />
+                  </div>
+                  <div>
+                    <el-tooltip class="box-item" effect="dark" :content="row.title" placement="top">
+                      <div class="double-line">{{ row.title ? row.title : '--' }}</div>
+                    </el-tooltip>
+                    <span
+                      >ASIN:
+                      <span class="data-color" style="margin-right: 8px">{{ row.asin ? row.asin : '--' }}</span>
+                    </span>
                   </div>
                 </div>
               </template>
@@ -114,7 +119,7 @@
             </el-table-column>
             <el-table-column label="操作" width="60">
               <template #default="{ row }">
-                <el-button link type="danger" size="small" @click="handleButtonClick(row)">删除</el-button>
+                <el-button link type="danger" size="small" @click="singleDelete(row)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -128,22 +133,27 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, reactive, CSSProperties } from 'vue'
-import { request } from '/@/utils/service'
+import { ElMessage, type TabsPaneContext } from 'element-plus'
+import { storeToRefs } from 'pinia'
+import { Ref, inject, onMounted, ref, watch } from 'vue'
+import { postCustomOperation } from '../api/index'
 import CategorySearch from './CategorySearch.vue'
 import SingleSearch from './SingleSearch.vue'
-import { ElMessage, type FormInstance, type FormRules, type TabsPaneContext } from 'element-plus'
 import { useShopInfo } from '/@/stores/shopInfo'
-import { storeToRefs } from 'pinia'
+import emitter from '/@/utils/emitter'
 
+const respCampaignId = inject<Ref>('respCampaignId')
+const respAdGroupId = inject<Ref>('respAdGroupId')
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
 
 // tab栏
 const topTabName = ref('category')
 
-function handleClick(tab: TabsPaneContext, event: Event) {
-  // console.log(tab, event)
+function handleTabClick(tab: TabsPaneContext, event: Event) {
+  if (tab.props.label == '搜索') {
+    emitter.emit('get-category-tree-data')
+  }
 }
 
 // tab栏顶部固定部分功能
@@ -158,7 +168,7 @@ const bidTypeOptions = [
     label: '自定义竞价',
   },
 ]
-const bid = ref('0.75')
+const bid = ref('0.3')
 
 watch(
   bidType,
@@ -166,138 +176,95 @@ watch(
     if (bidType.value == '1') {
       bid.value = ''
     } else {
-      bid.value = '0.75'
+      bid.value = '0.3'
     }
   },
   { immediate: true }
 )
 
 // 亚马逊受众tab的 下拉框和输入框
-const audienceType = ref('1')
-const audienceTypeOptions = [
-  {
-    value: '1',
-    label: '所有受众',
-  },
-  {
-    value: '2',
-    label: '生活方式',
-  },
-  {
-    value: '3',
-    label: '兴趣',
-  },
-  {
-    value: '4',
-    label: '生活事件',
-  },
-  {
-    value: '5',
-    label: '场内客群',
-  },
-]
+// const audienceType = ref('1')
+// const audienceTypeOptions = [
+//   {
+//     value: '1',
+//     label: '所有受众',
+//   },
+//   {
+//     value: '2',
+//     label: '生活方式',
+//   },
+//   {
+//     value: '3',
+//     label: '兴趣',
+//   },
+//   {
+//     value: '4',
+//     label: '生活事件',
+//   },
+//   {
+//     value: '5',
+//     label: '场内客群',
+//   },
+// ]
 
 const keywordInput = ref('') // 关键词过滤输入框
 
 // 浏览再营销下的tab栏
 const viewsTabName = ref('advice')
-const viewsLookBack = ref('7')
-const viewsLookBackOptions = [
-  {
-    value: '7',
-    label: '7天',
-  },
-  {
-    value: '14',
-    label: '14天',
-  },
-  {
-    value: '30',
-    label: '30天',
-  },
-  {
-    value: '60',
-    label: '60天',
-  },
-  {
-    value: '90',
-    label: '90天',
-  },
-]
+// const viewsLookBack = ref('7')
+// const viewsLookBackOptions = [
+//   {
+//     value: '7',
+//     label: '7天',
+//   },
+//   {
+//     value: '14',
+//     label: '14天',
+//   },
+//   {
+//     value: '30',
+//     label: '30天',
+//   },
+//   {
+//     value: '60',
+//     label: '60天',
+//   },
+//   {
+//     value: '90',
+//     label: '90天',
+//   },
+// ]
 
 // 购买再营销下的tab栏
 const purchasesTabName = ref('advice')
-const purchasesLookBack = ref('7')
-const purchasesLookBackOptions = [
-  {
-    value: '7',
-    label: '7天',
-  },
-  {
-    value: '14',
-    label: '14天',
-  },
-  {
-    value: '30',
-    label: '30天',
-  },
-  {
-    value: '60',
-    label: '60天',
-  },
-  {
-    value: '90',
-    label: '90天',
-  },
-]
-
-// 已添加的表格数据
-const addedTableData = ref([])
-
-function handleAddToTable(data) {
-  // 检查该数据是否已经存在于 addedTableData 中
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
-  if (!exists) {
-    addedTableData.value.push(data)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
-}
-
-// 点击细化后弹窗确认触发
-function handleFormSubmitted(data) {
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
-
-  if (!exists) {
-    // addedTableData.value.push(data)
-    const deliveryText = data.delivery === 'eligible' ? '具备Prime资格' : data.delivery === 'diseligible' ? '不具备Prime资格' : '所有'
-
-    const tableRow = {
-      categoryId: data.categoryId,
-      dialogTitle: data.dialogTitle, // 分类名称
-      prices: `\$${data.prices.lowest} ~ \$${data.prices.highest}`, // 价格范围
-      delivery: deliveryText, // 配送选项
-      // 其他需要的字段...
-    }
-    addedTableData.value.push(tableRow) // 添加到表格数据
-    console.log('data', data)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
-}
+// const purchasesLookBack = ref('7')
+// const purchasesLookBackOptions = [
+//   {
+//     value: '7',
+//     label: '7天',
+//   },
+//   {
+//     value: '14',
+//     label: '14天',
+//   },
+//   {
+//     value: '30',
+//     label: '30天',
+//   },
+//   {
+//     value: '60',
+//     label: '60天',
+//   },
+//   {
+//     value: '90',
+//     label: '90天',
+//   },
+// ]
 
-// 获取单个商品的搜索数据
 // 获取单个商品的搜索数据
 function handleSelectedCommodities(selectedCommodities) {
   const selectedMap = new Map(selectedCommodities.map((item) => [item.asin, item]))
 
-  // 移除取消勾选的商品,只针对 source 为 'single' 的商品
   addedTableData.value = addedTableData.value.filter((item) => item.source !== 'single' || selectedMap.has(item.asin))
 
   // 遍历所有选中的商品
@@ -308,8 +275,8 @@ function handleSelectedCommodities(selectedCommodities) {
       // 如果不存在,则添加新商品到 addedTableData
       addedTableData.value.push({
         ...commodity,
-        source: 'single', // 添加标识
-        // 这里可以根据需要添加其他属性或转换数据
+        bid: bid.value,
+        source: 'single',
       })
     } else {
       ElMessage({
@@ -320,30 +287,123 @@ function handleSelectedCommodities(selectedCommodities) {
   })
 }
 
+// 已添加的表格数据
+const addedTableData = ref([])
 
 // 删除所有Table数据
 function handleDeleteAll() {
   addedTableData.value = []
 }
 
-// 操作列按钮功能
-function handleButtonClick(row) {
-  addedTableData.value = addedTableData.value.filter((item) => item.cid !== row.cid)
+// 单个删除功能
+function singleDelete(row) {
+  addedTableData.value = addedTableData.value.filter((item) => item !== row)
 }
 
-// 点击保存触发
-function handleSave() {
-  createPromote()
+function handleAddToTable(data) {
+  // 检查该数据是否已经存在于 addedTableData 中
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
+  // if (!exists) {
+  const tableRow = {
+    type: 'c',
+    cna: data.cna,
+    bid: bid.value,
+    classificationId: data.cid,
+  }
+  addedTableData.value.push(tableRow)
+  // addedTableData.value.push(data)
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
-async function createPromote() {
-  // try {
-  //   const body = {}
-  //   const response = await xx()
-  //   console.log('response', response.data)
-  // } catch (error) {
-  // console.log('error: ', error)
-  // }
+// 点击细化后弹窗确认触发
+function handleRefine(data) {
+  const tableRow = {
+    type: 'c',
+    brand: data.selectedLabels,
+    bid: bid.value,
+    brandId: data.selectedBrands,
+    classificationId: data.cid,
+    delivery: data.delivery,
+    dialogTitle: data.dialogTitle,
+    low_prices: data.prices.lowest,
+    high_prices: data.prices.highest,
+    low_rating: data.starRating[0],
+    high_rating: data.starRating[1],
+  }
+  addedTableData.value.push(tableRow)
+}
+
+const deliveryMap = {
+  eligible: '具备Prime资格',
+  diseligible: '不具备Prime资格',
+  all: '所有',
+}
+const promoteGoodsAsin = ref('')
+
+function addDynamicSegment() {
+  const newProduct = {
+    currentTitle: '与广告商品相似',
+    type: 'p',
+    // lookback: viewsLookBack.value,
+    asin: promoteGoodsAsin.value,
+    bid: bid.value,
+    productType: 'similarProduct',
+  }
+  const exists = addedTableData.value.some((item) => item.type === newProduct.type && item.productType === newProduct.productType)
+
+  if (!exists) {
+    addedTableData.value.push(newProduct)
+  } else {
+    ElMessage({
+      message: '已经添加了相关商品,不能重复添加',
+      type: 'warning',
+    })
+  }
+}
+
+// 点击保存触发
+const loading = ref(false)
+const successCount = ref('')
+const failureCount = ref('')
+
+async function handleSave() {
+  console.log('addedTableData', addedTableData.value)
+  const body = {
+    profile_id: profile.value.profile_id,
+    campaignId: respCampaignId.value,
+    adGroupId: respAdGroupId.value,
+    tactic: 'T00020',
+    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) {
@@ -353,6 +413,12 @@ function changeTableHeader(row) {
     }
   }
 }
+
+onMounted(() => {
+  emitter.on('send-firstAsin', (value: any) => {
+    promoteGoodsAsin.value = value
+  })
+})
 </script>
 
 <style scoped>

+ 254 - 105
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,25 +113,32 @@
         <!-- 右侧内容区 -->
         <div class="right-container">
           <div class="right-container-top">
-            <div style="padding-left: 15px; font-size: 15px">
-              <span style="color: #8e9095">已添加: </span> <span style="font-weight: 500">{{ addedTableData.length }}</span>
-            </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>
             <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 v-if="row.cna || row.dialogTitle">浏览再营销</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>
                 <div v-if="row.cna || row.dialogTitle">
                   分类: <span style="color: black">{{ row.cna ? row.cna : row.dialogTitle }}</span>
                 </div>
+                <div v-if="row.brand">
+                  品牌: <span style="color: black">{{ row.brand ? row.brand : '--' }}</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>
@@ -140,6 +160,7 @@
               </template>
             </el-table-column>
             <el-table-column prop="address" label="当前建议竞价" width="160">
+              <!-- TODO: 后续加入建议竞价 -->
               <template #default="{ row }">
                 <div>$</div>
                 <div>$</div>
@@ -147,7 +168,7 @@
             </el-table-column>
             <el-table-column label="操作" width="60">
               <template #default="{ row }">
-                <el-button link type="danger" size="small" @click="handleButtonClick(row)">删除</el-button>
+                <el-button link type="danger" size="small" @click="singleDelete(row)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -161,15 +182,18 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, reactive, CSSProperties } from 'vue'
-import { request } from '/@/utils/service'
+import { ElMessage, type TabsPaneContext } from 'element-plus'
+import { storeToRefs } from 'pinia'
+import { Ref, inject, onMounted, onUnmounted, ref, watch } from 'vue'
+import { getAudiencesList, postCustomOperation } from '../api/index'
 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 { 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)
 
@@ -177,7 +201,9 @@ const { profile } = storeToRefs(shopInfo)
 const topTabName = ref('audience')
 
 function handleClick(tab: TabsPaneContext, event: Event) {
-  // console.log(tab, event)
+  if (tab.props.label == '购买再营销') {
+    emitter.emit('tree-node-data')
+  }
 }
 
 // tab栏顶部固定部分功能
@@ -289,40 +315,46 @@ const purchasesLookBackOptions = [
 const addedTableData = ref([])
 
 function browseOrientation(data) {
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
-  if (!exists) {
-    const dataWithLookback = {
+
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
+  // if (!exists) {
+    const tableRow = {
+      currentTitle: '浏览再营销',
       type: 'c',
       tactictype: 'views',
+      cna: data.cna,
       lookback: viewsLookBack.value,
       bid: bid.value,
-      classificationId: data.cid
+      classificationId: data.cid,
     }
-    console.log('dataWithLookback', dataWithLookback)
-    addedTableData.value.push(dataWithLookback)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+    addedTableData.value.push(tableRow)
+  // } 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,
+      currentTitle: '购买再营销',
+      type: 'c',
+      tactictype: 'purchases',
+      cna: data.cna,
       lookback: viewsLookBack.value,
+      bid: bid.value,
+      classificationId: data.cid,
     }
-    console.log('dataWithLookback', dataWithLookback)
     addedTableData.value.push(dataWithLookback)
-  } else {
-    ElMessage({
-      message: '请勿重复添加',
-      type: 'warning',
-    })
-  }
+  // } else {
+  //   ElMessage({
+  //     message: '请勿重复添加',
+  //     type: 'warning',
+  //   })
+  // }
 }
 
 // 点击细化后弹窗确认触发
@@ -333,11 +365,11 @@ const deliveryMap = {
 }
 
 function browseRefine(data) {
-  const exists = addedTableData.value.some((item) => item.cid === data.cid)
+  // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
 
-  if (!exists) {
-    console.log('data', data)
+  // if (!exists) {
     const tableRow = {
+      currentTitle: '浏览再营销',
       type: 'c',
       tactictype: 'views',
       lookback: viewsLookBack.value,
@@ -345,50 +377,48 @@ function browseRefine(data) {
       brandId: data.selectedBrands,
       classificationId: data.cid,
       delivery: data.delivery,
-      // categoryId: data.categoryId,
       dialogTitle: data.dialogTitle, // 分类名称
+      brand: data.selectedLabels,
       low_prices: data.prices.lowest,
       high_prices: data.prices.highest,
       low_rating: data.starRating[0],
       high_rating: data.starRating[1],
     }
     addedTableData.value.push(tableRow) // 添加到表格数据
-    console.log('tableRow', 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}`, // 价格范围
-      // 其他需要的字段...
+      brand: data.selectedLabels,
+      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',
+  //   })
+  // }
 }
 
 // 获取搜索的数据
@@ -426,13 +456,16 @@ async function getCustomData() {
 }
 
 function handleAddButtonClick(row) {
-  // 检查该行数据是否已经存在于 addedTableData 中
   const exists = addedTableData.value.some((item) => item.audienceId === row.audienceId)
   if (!exists) {
-    // 如果不存在,则添加它到 addedTableData
-    addedTableData.value.push(row)
+    const dataWithLookback = {
+      ...row,
+      tactictype:"audience",
+      audiencevalue: row.audienceId,
+      bid: bid.value,
+    }
+    addedTableData.value.push(dataWithLookback)
   } else {
-    // 如果已存在,显示一个警告消息
     ElMessage({
       message: `选项 ${row.audienceName} 已经添加,不能重复添加`,
       type: 'warning',
@@ -446,49 +479,151 @@ function handleDeleteAll() {
 }
 
 // 单独删除功能
-function handleButtonClick(row) {
-  if ('cid' in row) {
-    // 如果行数据包含 cid 属性,使用 cid 来过滤
-    addedTableData.value = addedTableData.value.filter((item) => item.cid !== row.cid)
-  } else if ('audienceId' in row) {
-    addedTableData.value = addedTableData.value.filter((item) => item.audienceId !== row.audienceId)
-  } else if ('categoryId' in row) {
-    addedTableData.value = addedTableData.value.filter((item) => item.cid !== row.cid)
-  }
+function singleDelete(row) {
+  addedTableData.value = addedTableData.value.filter((item) => item !== row)
+}
+
+const promoteGoodsAsin = ref('')
+const productTypeDict = {
+  exactProduct: '推广商品',
+  relatedProduct: '与推广的商品相关',
+  similarProduct: '与推广商品类似的商品',
 }
 
 function addPromoteProduct() {
-  const newProduct = {
-    type: 'p',
-    tactictype: 'purchases',
-    lookback: viewsLookBack.value,
-    asin: '',
-    bid: 0.2,
-    productType: 'relatedProduct',
+  // 通过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',
+      })
+    }
   }
-  // 例如 type, tactictype, productType 等字段来判断
-  const exists = addedTableData.value.some(
-    (item) => item.type === newProduct.type && item.tactictype === newProduct.tactictype && item.productType === newProduct.productType
-  )
+}
 
-  if (!exists) {
-    // 如果不存在,则添加它到 addedTableData
-    addedTableData.value.push(newProduct)
-  } else {
-    // 如果已存在,显示一个警告消息
-    ElMessage({
-      message: '已经添加了相关商品,不能重复添加',
-      type: 'warning',
-    })
+function addPromoteSimilar() {
+  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 addPromoteSimilar() {}
+// TODO: 测试state的值是paused, 上线后需要修改
+const loading = ref(false)
+const successCount = ref('')
+const failureCount = ref('')
 
-// 点击保存触发
-function handleSave() {
-  // createCustom()
-  console.log('addedTableData:', addedTableData.value)
+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) {
@@ -498,6 +633,19 @@ function changeTableHeader(row) {
     }
   }
 }
+
+onMounted(() => {
+  emitter.on('send-firstAsin', (value: any) => {
+    promoteGoodsAsin.value = value
+  })
+  emitter.on('respAdGroupId', (value: any) => {
+    respAdGroupId.value = value
+  })
+})
+
+onUnmounted(() => {
+  emitter.all.clear()
+})
 </script>
 
 <style scoped>
@@ -521,6 +669,7 @@ function changeTableHeader(row) {
   border: 1px solid #e5e7eb;
   height: 700px;
   margin-bottom: 20px;
+  border-radius: 6px;
 }
 .left-container {
   width: 50%;

+ 7 - 10
src/views/adManage/sd/campaigns/CreateCampaigns/component/PromoteProduct.vue

@@ -183,7 +183,7 @@ const buttons = [{ type: 'primary', text: '添加' }] as const
 const productTabs = ref('first')
 const rightSelect = ref('latest')
 const respCampaignId = inject<Ref>('respCampaignId')
-const respAdGroupId = ref('')
+const respAdGroupId = inject<Ref>('respAdGroupId')
 
 function setTableData(asin = '', sku = '') {
   return request({
@@ -305,11 +305,6 @@ function handleGoodsAdd() {
   }
 }
 
-// 点击Tab
-const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
-  console.log(tab, event)
-}
-
 function isItemInList(item, list) {
   return list.some((listItem) => listItem.sku === item.sku && listItem.asin === item.asin)
 }
@@ -348,6 +343,7 @@ async function createAds() {
     productLoading.value = false
     if (resp.data[0].adId) {
       addedTableData.value = []
+      addedAdsTableItems.value = []
       ElMessage({
         message: '商品创建成功',
         type: 'success',
@@ -377,18 +373,19 @@ function handleSizeChange(newSize) {
   currentPage.value = 1 // 重置到第一页
 }
 
-const headerCellStyle = (args) => {
+function headerCellStyle(args) {
   if (args.rowIndex === 0) {
     return {
       backgroundColor: 'rgba(245, 245, 245, 0.9)',
     }
   }
 }
+
 onMounted(() => {
   setTableData()
-  emitter.on('respAdGroupId', (value: any)=>{
-    respAdGroupId.value = value
-  })
+  // emitter.on('respAdGroupId', (value: any)=>{
+  //   respAdGroupId.value = value
+  // })
 })
 </script>
 

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

@@ -58,7 +58,6 @@ async function searchAsin() {
     }
     const response = await getCommodityCard(query)
     commodityData.value = response.data
-    console.log('commodityData.value', commodityData.value)
   } catch (error) {
     console.log('error:', error)
   } finally {

+ 15 - 13
src/views/adManage/sd/campaigns/CreateCampaigns/index.vue

@@ -1,25 +1,26 @@
 <template>
   <div class="page-container">
-    <AdCampaign @send-campaign="getCampaign"></AdCampaign>
-    <AdGroup></AdGroup>
+    <AdCampaign @send-campaign="getCampaign" @send-targetType="getTargetType"></AdCampaign>
+    <AdGroup @send-groupId="getGroupId"></AdGroup>
     <component :is="currentComponent"></component>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, computed, onUnmounted, provide } from 'vue'
+import { computed, provide, ref } from 'vue'
 import AdCampaign from './component/AdCampaign.vue'
 import AdGroup from './component/AdGroup.vue'
-import CustomTarget from './component/CustomTarget.vue'
 import ContentTarget from './component/ContentTarget.vue'
-import emitter from '/@/utils/emitter'
+import CustomTarget from './component/CustomTarget.vue'
 
 
 const respCampaignId = ref('')
 const respCampaignName = ref('')
+const respAdGroupId = ref('')
 
 provide('respCampaignId', respCampaignId)
 provide('respCampaignName', respCampaignName)
+provide('respAdGroupId', respAdGroupId)
 
 function getCampaign(data) {
   respCampaignId.value = data.id
@@ -27,10 +28,11 @@ function getCampaign(data) {
 }
 
 // 获取targetType的值渲染自定义定向
-const targetType = ref()
-emitter.on('send-targetType', (value: any) => {
-  targetType.value = value.value
-})
+const targetType = ref('T00030')
+
+function getTargetType(data) {
+  targetType.value = data.type
+}
 
 // 动态组件
 const currentComponent = computed(() => {
@@ -42,10 +44,10 @@ const currentComponent = computed(() => {
   }
 })
 
-// 接收数据端在组件卸载时解绑事件
-onUnmounted(() => {
-  emitter.off('send-targetType')
-})
+function getGroupId(data) {
+  respAdGroupId.value = data.adGroupId
+}
+
 </script>
 
 <style scoped>

+ 26 - 40
src/views/adManage/sp/campaigns/CreateCampaigns/index.vue

@@ -119,7 +119,7 @@
             <br />
             <el-form ref="adGroupRuleFormRef" :model="adGroupRuleForm" :rules="adGroupRules">
               <el-form-item required label="广告组名称" prop="adGroupName" style="width: 350px; margin-top: 20px">
-                <el-input v-model="adGroupRuleForm.adGroupName"  placeholder="请输入广告组名称"/>
+                <el-input v-model="adGroupRuleForm.adGroupName" placeholder="请输入广告组名称" />
               </el-form-item>
               <el-form-item required label="默认竞价" prop="defaultBidInp">
                 <el-input v-model="adGroupRuleForm.defaultBidInp" minlength="3" maxlength="4" style="width: 200px">
@@ -514,7 +514,7 @@
                   <template #header>
                     <div class="card-header">
                       <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
-                      <el-button class="button" text bg @click="delAllNegative">全部删除</el-button>
+                      <el-button class="button" text bg :disabled="!tableData.length" @click="delAllNegative">全部删除</el-button>
                     </div>
                   </template>
                   <div class="card-body">
@@ -815,13 +815,21 @@
                   <el-tab-pane label="输入" name="second">
                     <el-input
                       v-model="ruleForm.negativeGoodsTextarea"
+                      :disabled="true"
                       :rows="17"
                       type="textarea"
-                      placeholder="未完成"
                       maxlength="11000"
                       style="padding: 10px 10px" />
                     <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
-                      <el-button style="margin-right: 10px" type="primary" text bg @click="addNegativeGoods">添加</el-button>
+                      <el-button
+                        style="margin-right: 10px"
+                        type="primary"
+                        text
+                        bg
+                        :disabled="!ruleForm.negativeGoodsTextarea"
+                        @click="addNegativeGoods"
+                        >添加</el-button
+                      >
                     </div>
                   </el-tab-pane>
                 </el-tabs>
@@ -1049,7 +1057,7 @@ async function createCampaigns() {
 
 const adMixOptions = ref([])
 async function getAdMix() {
-  try { 
+  try {
     const resp = await request({
       url: '/api/ad_manage/portfolios/select_list',
       method: 'GET',
@@ -1064,7 +1072,6 @@ async function submitCampaignForm(formEl: FormInstance | undefined) {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
     if (valid) {
-      console.log('submit!')
       campaignLoading.value = true
       createCampaigns()
     } else {
@@ -1077,7 +1084,6 @@ async function submitForm(formEl: FormInstance | undefined) {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
     if (valid) {
-      console.log('submit!')
       createCampaigns()
     } else {
       console.log('error submit!', fields)
@@ -1147,7 +1153,6 @@ async function createGroups() {
       data: filteredRequestData,
     })
     respAdGroupId.value = resp.data.adGroupId
-    console.log('🚀 ~ createGroups ~ resp-->>', resp)
     adGroupLoading.value = false
     if (respAdGroupId.value) {
       ElMessage({
@@ -1167,7 +1172,6 @@ async function submitGroupsForm(formEl: FormInstance | undefined) {
   await formEl.validate((valid, fields) => {
     if (valid) {
       adGroupLoading.value = true
-      console.log('submit!')
       createGroups()
     } else {
       console.log('error submit!', fields)
@@ -1211,7 +1215,10 @@ function addSingleGoods(scope) {
   if (!isAlreadyAdded) {
     adsTableData.value.push(scope.row)
   } else {
-    console.log('Item is already added.')
+    ElMessage({
+      message: '已添加',
+      type: 'warning',
+    })
   }
 }
 
@@ -1236,7 +1243,6 @@ function delSingleGoods(scope) {
   const index = adsTableData.value.findIndex((item) => item.sku === scope.row.sku)
   if (index !== -1) {
     adsTableData.value.splice(index, 1)
-    console.log('Item removed successfully.')
   } else {
     console.log('Item not found.')
   }
@@ -1265,7 +1271,7 @@ function inpChange(e) {
 }
 
 function selChange(e) {
-  console.log('e', e)
+  // console.log('e', e)
   const value = e
   if (select.value === 'asin' && searchInp.value) {
     loading.value = true
@@ -1335,7 +1341,6 @@ async function createAds() {
       method: 'POST',
       data: filteredRequestData,
     })
-    console.log('🚀 ~ createAds ~ resp-->>', resp)
     commodityLoading.value = false
     if (resp.data.success.length > 0) {
       adsSave.value = false
@@ -1458,7 +1463,6 @@ async function createTargetGroup() {
       method: 'POST',
       data: filteredRequestData,
     })
-    console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
     targetGroupLoading.value = false
     if (respAdGroupId.value) {
       ElMessage({
@@ -1475,7 +1479,6 @@ async function createTargetGroup() {
 
 function submitTargetGroupForm() {
   targetGroupLoading.value = true
-  console.log('submit!')
   createTargetGroup()
 }
 
@@ -1510,7 +1513,6 @@ const rules = computed(() => ({
 
 // 当单选按钮变化时更新showCard状态
 function changeBid() {
-  console.log(ruleForm.autoRedirect)
   showCard.value = ruleForm.autoRedirect === 'targetBid'
 }
 // 切换投放类型
@@ -1747,8 +1749,6 @@ function singleGoodsSearchChaneged() {
   setSearchTableData()
 }
 function addSingleSearch(scope) {
-  console.log('🚀 ~ addSingleSearch ~ scope-->>', scope)
-
   const typesToAdd = []
   if (expand.value) {
     typesToAdd.push('ASIN_EXPANDED_FROM')
@@ -1783,28 +1783,26 @@ function addSingleSearch(scope) {
       }
       productOrientationTableData.value.push(newData)
     } else {
-      console.log(`${productType} item is already added.`)
+      ElMessage({
+        message: `${productType} 已添加`,
+        type: 'warning',
+      })
     }
   })
 }
 
 let selectedLabels = ref([]) // 选中的label数组
 function dialogSelectChange(event) {
-  console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
-
   // 使用 map 来转换每个选中项的 value 为其对应的 label
   selectedLabels.value = event.map((selectedValue) => {
     const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
     return selectedOption ? selectedOption.label : ''
   })
-
-  console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
 }
 
 let refineItem = ref([])
 // 细化按钮功能
 function refine(data) {
-  console.log('🚀 ~ refine ~ data-->>', data)
   commodityCount.value = []
   dialogTitle.value = data.cna
   categoryId.value = data.cid
@@ -1817,7 +1815,6 @@ function refine(data) {
 function dialogFormSubmit() {
   dialogFormRef.value.validate((valid) => {
     if (valid) {
-      console.log('表单提交')
       visible.value = false
       const dialogClassification = dialogTitle.value
       const dialogPrices_low = dialogForm.prices.lowest
@@ -1826,7 +1823,6 @@ function dialogFormSubmit() {
       const ratingLow = dialogStartRating[0]
       const ratingHigh = dialogStartRating[1]
       const dialogDelivery = dialogForm.delivery
-      console.log('🚀 ~ dialogFormRef.value.validate ~ dialogDelivery-->>', dialogDelivery)
       const deliveryMap = {
         all: '所有',
         eligible: '具备Prime资格',
@@ -1860,7 +1856,6 @@ function dialogFormSubmit() {
           delivery: dialogDelivery,
           deliveryText: deliveryMap[dialogDelivery],
         }
-        console.log('🚀 ~ dialogFormRef.value.validate ~ refineObj-->>', refineObj)
         productOrientationTableData.value.push(refineObj)
       })
     } else {
@@ -1871,7 +1866,6 @@ function dialogFormSubmit() {
 
 // 定向按钮功能
 function orientate(node, data) {
-  console.log('🚀 ~ orientate ~ data-->>', data)
   const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
 
   let bidValue = null
@@ -1895,19 +1889,16 @@ function orientate(node, data) {
 
 let productTargetBidList = ref([])
 async function productTagetSave() {
-  console.log('tableData', productOrientationTableData.value)
   // 检查是否存在 bid 为空的行
   const hasEmptyBid = productOrientationTableData.value.some((row) => row.bid == null || row.bid === '')
   // 直接返回,不继续执行
   if (hasEmptyBid) {
-    console.log('存在空的 bid,不发送请求')
     ElMessage.error('存在空的 bid,无法创建商品!')
     return
   }
   productOrientationTableData.value.forEach((row) => {
     productTargetBidList.value.push(row.bid)
   })
-  console.log('productTargetBidList', productTargetBidList.value)
   productOrientationLoading.value = true
   try {
     const requestData = {
@@ -1924,7 +1915,6 @@ async function productTagetSave() {
       data: filteredRequestData,
     })
 
-    console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
     productOrientationLoading.value = false
 
     if (respAdGroupId.value) {
@@ -2152,7 +2142,6 @@ function delSingleNegative(scope) {
     // 从 negativeList 删除
     if (negativeList.length) {
       negativeList.splice(index, 1)
-      console.log(`已删除索引为 ${index} 的条目`)
     } else {
       console.log('无效的索引,无法删除条目')
     }
@@ -2177,7 +2166,6 @@ function delSingleNegative(scope) {
 
 async function negativeWordsSave() {
   negativeWordsLoading.value = true
-  console.log('negativeList', negativeList)
   try {
     const requestData = {
       profile_id: profile.value.profile_id,
@@ -2238,7 +2226,6 @@ function setNegativeTableData(asin = '') {
 
 // 输入tab的textarea
 function addNegativeGoods() {
-  console.log('ruleForm.negativeGoodsTextarea', ruleForm.negativeGoodsTextarea)
   loading.value = true
 
   setNegativeTableData(ruleForm.negativeGoodsTextarea)
@@ -2258,7 +2245,10 @@ function addSingleNegativeGoods(scope) {
   if (!isAlreadyAdded) {
     addedNegetiveTableData.value.push(scope.row)
   } else {
-    console.log('Item is already added.')
+    ElMessage({
+      message: '已存在',
+      type: 'warning',
+    })
   }
 }
 
@@ -2271,14 +2261,12 @@ function delSingleNegativeGoods(scope) {
 
   if (index !== -1) {
     addedNegetiveTableData.value.splice(index, 1)
-    console.log('Item removed successfully.')
   } else {
     console.log('Item not found.')
   }
 }
 
 function searchNegativeGoods(e) {
-  console.log(e)
   if (e === '') {
     negativeTableData.value = []
   } else {
@@ -2295,10 +2283,8 @@ function handleNegGoodsTabs(tab: TabsPaneContext, event: Event) {
 }
 
 async function negativeGoodsSave() {
-  console.log(addedNegetiveTableData.value)
   const asinList = addedNegetiveTableData.value.map((item) => item.asin)
   negativeGoodsLoading.value = true
-  console.log('addedNegetiveTableData', addedNegetiveTableData.value)
   try {
     const requestData = {
       profile_id: profile.value.profile_id,