Bläddra i källkod

✨ feat: SD新建广告活动: 内容相关投放已完成

WanGxC 1 år sedan
förälder
incheckning
556bb1c947

+ 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: 'AiTestX2024_06',
+  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()

+ 10 - 8
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: 'AiTestX2024_01',
+  groupName: 'AiTestX2024',
   adMix: '',
   startDate: '',
   endDate: '',
@@ -87,10 +87,9 @@ 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)
     }
   })
 }
@@ -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

+ 9 - 11
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>
@@ -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,12 +143,11 @@ async function setProductOrientationData() {
 }
 
 function dialogSelectChange(event) {
-  // 使用 map 来转换每个选中项的 value 为其对应的 label
-  // console.log('event', event)
-  // 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() {

+ 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>

+ 14 - 31
src/views/adManage/sd/campaigns/CreateCampaigns/component/CustomTarget.vue

@@ -113,15 +113,9 @@
         <!-- 右侧内容区 -->
         <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>
-
-            <!-- <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%">
@@ -134,6 +128,9 @@
                 <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> -</span>
@@ -163,6 +160,7 @@
               </template>
             </el-table-column>
             <el-table-column prop="address" label="当前建议竞价" width="160">
+              <!-- TODO: 后续加入建议竞价 -->
               <template #default="{ row }">
                 <div>$</div>
                 <div>$</div>
@@ -184,14 +182,13 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch, Ref, inject, reactive, CSSProperties, onUnmounted } 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, postCustomOperation } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
-import { storeToRefs } from 'pinia'
 import emitter from '/@/utils/emitter'
 
 
@@ -321,7 +318,7 @@ function browseOrientation(data) {
 
   // const exists = addedTableData.value.some((item) => item.classificationId === data.cid)
   // if (!exists) {
-    const dataWithLookback = {
+    const tableRow = {
       currentTitle: '浏览再营销',
       type: 'c',
       tactictype: 'views',
@@ -330,7 +327,7 @@ function browseOrientation(data) {
       bid: bid.value,
       classificationId: data.cid,
     }
-    addedTableData.value.push(dataWithLookback)
+    addedTableData.value.push(tableRow)
   // } else {
   //   ElMessage({
   //     message: '请勿重复添加',
@@ -381,6 +378,7 @@ function browseRefine(data) {
       classificationId: data.cid,
       delivery: data.delivery,
       dialogTitle: data.dialogTitle, // 分类名称
+      brand: data.selectedLabels,
       low_prices: data.prices.lowest,
       high_prices: data.prices.highest,
       low_rating: data.starRating[0],
@@ -408,6 +406,7 @@ function buyRefine(data) {
       classificationId: data.cid,
       delivery: data.delivery,
       dialogTitle: data.dialogTitle, // 分类名称
+      brand: data.selectedLabels,
       low_prices: data.prices.lowest,
       high_prices: data.prices.highest,
       low_rating: data.starRating[0],
@@ -472,7 +471,6 @@ function handleAddButtonClick(row) {
       type: 'warning',
     })
   }
-  console.log('addedTableData', addedTableData.value)
 }
 
 // 删除所有Table数据
@@ -482,22 +480,7 @@ function handleDeleteAll() {
 
 // 单独删除功能
 function singleDelete(row) {
-  console.log('row', 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)
-  } 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))
-  } else if ('audiencevalue' in row) {
-    addedTableData.value = addedTableData.value.filter((item) => item.audiencevalue !== row.audiencevalue)
-  }
+  addedTableData.value = addedTableData.value.filter((item) => item !== row)
 }
 
 const promoteGoodsAsin = ref('')
@@ -604,7 +587,7 @@ function addPromoteSimilar() {
   }
 }
 
-// TODO: 测试state的值是paused
+// TODO: 测试state的值是paused, 上线后需要修改
 const loading = ref(false)
 const successCount = ref('')
 const failureCount = ref('')

+ 4 - 4
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({
@@ -383,9 +383,9 @@ function headerCellStyle(args) {
 
 onMounted(() => {
   setTableData()
-  emitter.on('respAdGroupId', (value: any)=>{
-    respAdGroupId.value = value
-  })
+  // emitter.on('respAdGroupId', (value: any)=>{
+  //   respAdGroupId.value = value
+  // })
 })
 </script>
 

+ 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>