瀏覽代碼

Merge branch 'wang' into test

WanGxC 1 年之前
父節點
當前提交
3129e75d9a

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

@@ -235,8 +235,7 @@ watch(
       // console.log(`Selected page URL: ${selectedPage.storePageUrl}`)
       setTimeout(() => {
         emitter.emit('page', selectedPage.storePageUrl)
-      }, 2000);
-      
+      }, 2000)
     } else {
       console.log('No page selected or matching page not found')
     }
@@ -307,12 +306,12 @@ function shopChanged() {
   }, 2000)
 }
 
-watch([adFormatRadio, arrivalsRadio], ()=>{
+watch([adFormatRadio, arrivalsRadio], () => {
   ruleForm.shop = ''
   ruleForm.page = ''
-  
+
   if (ruleFormRef.value) {
-    ruleFormRef.value.clearValidate(['shop', 'page']);
+    ruleFormRef.value.clearValidate(['shop', 'page'])
   }
 })
 

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

@@ -8,22 +8,6 @@ export function getAdMixSelect() {
   })
 }
 
-export function postCampaignsData(filteredRequestData) {
-  return request({
-      url: '/api/ad_manage/sbcampaigns/create/',
-      method: 'post',
-      data: filteredRequestData,
-  })
-}
-
-export function postGroupData(filteredRequestData) {
-  return request({
-      url: '/api/ad_manage/sbgroups/create/',
-      method: 'post',
-      data: filteredRequestData,
-  })
-}
-
 export function postNegativeWordData(filteredRequestData) {
   return request({
       url: '/api/ad_manage/sptargets/add/negative/keywords/',
@@ -87,10 +71,26 @@ export function getVideoAssets(query) {
   })
 }
 
+export function postCampaignsData(filteredRequestData) {
+  return request({
+      url: '/api/ad_manage/sdcampaigns/create/',
+      method: 'post',
+      data: filteredRequestData,
+  })
+}
+
 export function videoDetailCreate(obj) {
   return request({
       url: '/api/ad_manage/sbads/video/create/',
       method: 'post',
       data: obj,
   })
+}
+
+export function postAdGroup(body) {
+  return request({
+      url: '/api/ad_manage/sdgroups/create/',
+      method: 'post',
+      data: body,
+  })
 }

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

@@ -146,14 +146,13 @@ async function createCampaigns() {
   const campaignData = {
     profile_id: profile.value.profile_id,
     budget: campaignRuleForm.budget,
+    budgetType: 'daily',
     name: campaignRuleForm.campaignName,
-    bidOptimizationStrategy: '',
     startDate: campaignRuleForm.startDate,
     endDate: campaignRuleForm.endDate,
-    smartDefault: 'MANUAL',
-    costType: 'CPC',
-    goal: 'PAGE_VISIT',
-    state: 'ENABLED',
+    costType: 'cpc',
+    state: 'paused',
+    tactic: targetType.value,
   }
 
   try {
@@ -174,11 +173,11 @@ async function createCampaigns() {
 }
 
 // 提供数据给父组件
-const emit = defineEmits(['update-campaign'])
+const emit = defineEmits(['send-campaign'])
 
 watch([respCampaignId, respCampaignName], () => {
   if (respCampaignId.value && respCampaignName.value) {
-    emit('update-campaign', {
+    emit('send-campaign', {
       id: respCampaignId.value,
       name: respCampaignName.value,
     })

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

@@ -5,56 +5,60 @@
         <span class="custom-card-icon">|</span>
         <span class="custom-card-Text">广告组</span>
       </div>
-      <el-form
-        ref="campaignRuleFormRef"
-        :model="ruleForm"
-        :rules="rules"
-        label-position="top"
-        label-width="120px"
-        size="default"
-        status-icon>
-        <el-form-item label="广告组名称" prop="groupName">
-          <el-input v-model="ruleForm.groupName" style="width: 310px"></el-input>
-        </el-form-item>
-        <el-form-item label="竞价优化" prop="groupName">
-          <div>
-            <el-radio-group v-model="bidOptimization" class="custom-radio-group">
-              <el-radio label="1" size="large" border class="custom-radio-group-item" style="margin: 0;">
-                <div>
-                <div>针对页面访问量</div>
-                 <div style="color: #8c8c8c; margin-top: -5px;"> 我们将优化您的竞价以获得更高的点击率。通过向更有可能点击广告的顾客展示您的广告来提高商品购买意向</div>
-                </div>
-              </el-radio>
-              <el-radio label="2" size="large" border class="custom-radio-group-item" style="margin: 10px 0;">
-                针对转化率
-                <div style="color: #8c8c8c; margin-top: -5px;"> 我们将优化您的竞价以获得更高的转化率。通过向更有可能购买商品的顾客展示您的广告来提高销量</div>
-              </el-radio>
-              <el-radio label="3" size="large" border class="custom-radio-group-item">
-                针对可见展示量进行优化
-                <div style="color: #8c8c8c; margin-top: -5px;"> 我们将优化您的竞价以获得更高的可见展示次数。通过在亚马逊上向尽可能多的顾客展示您的广告来提高商品知名度</div>
-              </el-radio>
-            </el-radio-group>
-          </div>
-        </el-form-item>
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-position="top" label-width="120px" size="default" status-icon>
+        <el-card shadow="never" body-style="padding-bottom: 0px" v-loading="adGroupLoading">
+          <el-form-item label="广告组名称" prop="groupName">
+            <el-input v-model="ruleForm.groupName" style="width: 310px"></el-input>
+          </el-form-item>
+          <el-form-item label="竞价优化" prop="groupName">
+            <div>
+              <el-radio-group v-model="bidOptimization" class="custom-radio-group">
+                <el-radio label="reach" size="large" border class="custom-radio-group-item" style="margin: 0">
+                  <div>
+                    <div>针对页面访问量</div>
+                    <div style="color: #8c8c8c; margin-top: -5px">
+                      我们将优化您的竞价以获得更高的点击率。通过向更有可能点击广告的顾客展示您的广告来提高商品购买意向
+                    </div>
+                  </div>
+                </el-radio>
+                <el-radio label="clicks" size="large" border class="custom-radio-group-item" style="margin: 10px 0">
+                  针对转化率
+                  <div style="color: #8c8c8c; margin-top: -5px">
+                    我们将优化您的竞价以获得更高的转化率。通过向更有可能购买商品的顾客展示您的广告来提高销量
+                  </div>
+                </el-radio>
+                <el-radio label="conversions" size="large" border class="custom-radio-group-item">
+                  针对可见展示量进行优化
+                  <div style="color: #8c8c8c; margin-top: -5px">
+                    我们将优化您的竞价以获得更高的可见展示次数。通过在亚马逊上向尽可能多的顾客展示您的广告来提高商品知名度
+                  </div>
+                </el-radio>
+              </el-radio-group>
+            </div>
+          </el-form-item>
+          <el-form-item style="margin: 20px 0 -10px 48%">
+            <el-button type="primary" plain :disabled="!respCampaignId" @click="submitForm(ruleFormRef)">保存</el-button>
+          </el-form-item>
+        </el-card>
         <PromoteProduct></PromoteProduct>
-        <el-form-item style="margin: 20px 0 -10px 48%">
-          <el-button type="primary" plain @click="submitForm(ruleFormRef)">保存</el-button>
-        </el-form-item>
       </el-form>
     </el-card>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, reactive, ref, watch, defineEmits } from 'vue'
+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 { useShopInfo } from '/@/stores/shopInfo'
+import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
+const respCampaignId = inject<Ref>('respCampaignId')
 
 const ruleFormRef = ref<FormInstance>()
 interface ruleForm {
@@ -84,6 +88,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   await formEl.validate((valid, fields) => {
     if (valid) {
       console.log('submit!')
+      crateAdGroup()
     } else {
       console.log('error submit!', fields)
     }
@@ -91,7 +96,45 @@ const submitForm = async (formEl: FormInstance | undefined) => {
 }
 
 // 竞价优化按钮组
-const bidOptimization = ref('1')
+const bidOptimization = ref('reach')
+
+// 创建广告组
+const adGroupLoading = ref(false)
+const respAdGroupId = ref('')
+
+async function crateAdGroup() {
+  adGroupLoading.value = true
+  try {
+    const body = {
+      profile_id: profile.value.profile_id,
+      campaignId: respCampaignId.value,
+      defaultBid: 0.1,
+      bidOptimization: bidOptimization.value,
+      state: 'paused',
+      creativeType: 'IMAGE',
+    }
+    const response = await postAdGroup(body)
+    if (response.data.adGroupId) {
+      respAdGroupId.value = response.data.adGroupId
+      ElMessage({ message: '广告活动创建成功', type: 'success' })
+    } else {
+      ElMessage.error('广告活动创建失败!')
+    }
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    adGroupLoading.value = false
+  }
+}
+
+watch(
+  () => respAdGroupId.value,
+  () => {
+    setTimeout(() => {
+      emitter.emit('respAdGroupId', respAdGroupId.value)
+    }, 2000)
+  }
+)
 </script>
 
 <style scoped>

+ 51 - 24
src/views/adManage/sd/campaigns/CreateCampaigns/component/CommodityOperate.vue → src/views/adManage/sd/campaigns/CreateCampaigns/component/BrowseSearch.vue

@@ -1,19 +1,19 @@
 <template>
   <div v-loading="containerLoading">
-  <el-scrollbar height="450px">
-    <el-tree :data="searchClassifyTableData" :props="defaultProps">
-      <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>
+    <el-scrollbar height="450px">
+      <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>
+            </span>
           </span>
-        </span>
-      </template>
-    </el-tree>
-  </el-scrollbar>
-</div>
+        </template>
+      </el-tree>
+    </el-scrollbar>
+  </div>
   <el-dialog v-model="visible" :title="`细化分类: ${dialogTitle}`" @close="dialogClose" destroy-on-close>
     <div style="display: flex; justify-content: space-between">
       <span>根据特定品牌、价格范围、星级和Prime配送资格,细化分类</span>
@@ -67,9 +67,7 @@ import { request } from '/@/utils/service'
 import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { storeToRefs } from 'pinia'
-
-
-const emit = defineEmits(['add-to-table']);
+import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -151,8 +149,6 @@ function dialogSelectChange(event) {
     const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
     return selectedOption ? selectedOption.label : ''
   })
-
-  console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
 }
 
 function resetDialogForm() {
@@ -231,7 +227,6 @@ async function setDialogOption() {
 // 细化按钮功能
 let refineItem = ref([])
 function refine(data) {
-  console.log('🚀 ~ refine ~ data-->>', data)
   commodityCount.value = []
   dialogTitle.value = data.cna
   categoryId.value = data.cid
@@ -241,19 +236,51 @@ function refine(data) {
   setDialogOption()
 }
 
+const emit = defineEmits(['add-to-table','form-submitted'])
+
 // 定向按钮功能
 function orientate(node, data) {
-  console.log('🚀 ~ orientate ~ data-->>', data)
-  const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
-  emit('add-to-table', data);
+  productOrientationTableData.value.some((item) => item.cid === data.cid)
+  emit('add-to-table', data)
 }
 
-function dialogFormSubmit() {}
+// 弹窗确定按钮功能
+async function dialogFormSubmit() {
+  // 验证表单数据
+  const isValid = await dialogFormRef.value.validate()
+  if (!isValid) {
+    console.log('表单数据验证失败')
+    return
+  }
+
+  // 封装数据
+  const formData = {
+    dialogTitle: dialogTitle.value,
+    cid: categoryId.value,
+    prices: {
+      lowest: dialogForm.prices.lowest,
+      highest: dialogForm.prices.highest,
+    },
+    starRating: dialogForm.starRating,
+    selectedBrands: dialogForm.dialogselectValue,
+    delivery: dialogForm.delivery,
+    isCount: dialogForm.isCount,
+    commodityCount: commodityCount.value,
+    selectedLabels: selectedLabels.value, // 如果需要选中的标签也发送到父组件
+  }
+
+  // 使用emit发送封装好的数据到父组件
+  emit('form-submitted', formData);
+  // emitter.emit('form-submitted', formData)
+
+  // 关闭弹窗
+  visible.value = false
+  resetDialogForm()
+}
 
 onMounted(() => {
   setProductOrientationData()
 })
-
 </script>
 
 <style scoped>

+ 296 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/BuySearch.vue

@@ -0,0 +1,296 @@
+<template>
+  <div v-loading="containerLoading">
+    <el-scrollbar height="450px">
+      <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>
+            </span>
+          </span>
+        </template>
+      </el-tree>
+    </el-scrollbar>
+  </div>
+  <el-dialog v-model="visible" :title="`细化分类: ${dialogTitle}`" @close="dialogClose" destroy-on-close>
+    <div style="display: flex; justify-content: space-between">
+      <span>根据特定品牌、价格范围、星级和Prime配送资格,细化分类</span>
+      <span>
+        <el-checkbox v-model="dialogForm.isCount" label="显示商品数量" @change="isCountChanged" />
+      </span>
+    </div>
+    <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-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="prices" style="padding-left: 112px; margin-top: 10px">
+        <span style="margin-right: 10px; color: #616266; font-weight: 500">价格范围</span>
+        <el-input-number v-model="dialogForm.prices.lowest" :min="1" :controls="false" placeholder="无最低商品价格" />
+        --
+        <el-input-number v-model="dialogForm.prices.highest" :min="1" :controls="false" placeholder="无最高商品价格" />
+      </el-form-item>
+      <el-form-item prop="starRating" style="padding-left: 85px; margin-top: 10px">
+        <span style="margin-right: 15px; color: #616266; font-weight: 500">查看星级评定</span>
+        <el-slider v-model="dialogForm.starRating" range show-stops :max="5" :marks="marks" style="width: 70%" />
+      </el-form-item>
+      <el-form-item prop="delivery" style="padding-left: 140px; margin-top: 30px">
+        <span style="margin-right: 10px; color: #616266; font-weight: 500">配送</span>
+        <el-radio-group v-model="dialogForm.delivery">
+          <el-radio label="all" style="font-weight: 400">所有</el-radio>
+          <el-radio label="eligible" style="font-weight: 400">具备Prime资格</el-radio>
+          <el-radio label="diseligible" style="font-weight: 400">不具备Prime资格</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div style="display: flex; justify-content: space-between">
+        <span v-loading="countLoadig">
+          定位到的商品数量: <span v-if="dialogForm.isCount == true">{{ commodityCount[0]?.min }} - {{ commodityCount[0]?.max }}</span>
+        </span>
+        <span class="dialog-footer">
+          <el-button @click="visible = false">取消</el-button>
+          <el-button type="primary" @click="dialogFormSubmit">确定</el-button>
+        </span>
+      </div>
+    </template>
+  </el-dialog>
+</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 emitter from '/@/utils/emitter'
+
+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([])
+let productOrientationTableData = ref([])
+const defaultProps = {
+  children: 'ch',
+  label: 'cna',
+}
+const visible = ref(false)
+let dialogTitle = ref('')
+let categoryId = ref('')
+let commodityCount = ref([])
+let currentDialogIndex = ref(0)
+let selectedLabels = ref([]) // 选中的label数组
+
+interface Mark {
+  style: CSSProperties
+  label: string
+}
+type Marks = Record<number, Mark | string>
+const marks = reactive<Marks>({
+  0: '0',
+  1: '1',
+  2: '2',
+  3: '3',
+  4: '4',
+  5: '5',
+})
+
+const dialogForm: any = reactive({
+  prices: {
+    lowest: undefined,
+    highest: undefined,
+  },
+  starRating: [0, 5],
+  dialogselectValue: [],
+  delivery: 'all',
+  isCount: false,
+})
+const dialogFormRef = ref()
+const dialogRules = reactive({
+  prices: [{ validator: validatePrices, trigger: 'blur' }],
+})
+
+async function validatePrices(rule, value) {
+  if (value.highest !== '' && value.lowest !== '' && value.highest <= value.lowest) {
+    return Promise.reject('最高价格必须大于最低价格')
+  }
+  return Promise.resolve()
+}
+
+async function setProductOrientationData() {
+  containerLoading.value = true
+  try {
+    const resp = await request({
+      url: '/api/ad_manage/targetable/categories/',
+      method: 'GET',
+      params: {
+        profile_id: profile.value.profile_id,
+      },
+    })
+    searchClassifyTableData.value = resp.data
+  } catch (error) {
+    console.error('请求失败:', error)
+  } 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 : ''
+  })
+}
+
+function resetDialogForm() {
+  dialogForm.prices.lowest = undefined
+  dialogForm.prices.highest = undefined
+  dialogForm.starRating = [0, 5]
+  dialogForm.dialogselectValue = []
+  dialogForm.delivery = 'all'
+  dialogForm.isCount = false
+}
+
+function dialogClose() {
+  currentDialogIndex.value++
+  resetDialogForm()
+  dialogForm.isCount = false
+  commodityCount.value = []
+  countLoadig.value = false
+}
+
+async function getCount(instanceId) {
+  try {
+    const resp = await request({
+      url: '/api/ad_manage/products/count/',
+      method: 'POST',
+      data: {
+        profile_id: profile.value.profile_id,
+        category_id: categoryId.value,
+      },
+    })
+    if (instanceId === currentDialogIndex.value) {
+      commodityCount.value = resp.data.AsinCounts
+    }
+  } catch (error) {
+    console.error('请求失败:', error)
+  } finally {
+    if (instanceId === currentDialogIndex.value) {
+      countLoadig.value = false
+    }
+  }
+}
+
+function isCountChanged() {
+  if (dialogForm.isCount) {
+    const instanceId = currentDialogIndex.value
+    countLoadig.value = true
+    getCount(instanceId)
+  } else {
+    countLoadig.value = false
+    commodityCount.value = []
+  }
+}
+
+async function setDialogOption() {
+  try {
+    const resp = await request({
+      url: '/api/ad_manage/categories/brands/',
+      method: 'GET',
+      params: {
+        profile_id: profile.value.profile_id,
+        category_id: categoryId.value,
+      },
+    })
+    const options = resp.data
+    dialogForm.dialogOptions = options.brands.map((brand) => {
+      return {
+        label: brand.name,
+        value: brand.id,
+      }
+    })
+    dialogSelectLoading.value = false
+  } catch (error) {
+    console.error('请求失败:', error)
+  }
+}
+
+// 细化按钮功能
+let refineItem = ref([])
+function refine(data) {
+  commodityCount.value = []
+  dialogTitle.value = data.cna
+  categoryId.value = data.cid
+  refineItem.value.push(data)
+  visible.value = true
+  dialogSelectLoading.value = true
+  setDialogOption()
+}
+
+const emit = defineEmits(['add-to-table','form-submitted'])
+
+// 定向按钮功能
+function orientate(node, data) {
+  productOrientationTableData.value.some((item) => item.cid === data.cid)
+  emit('add-to-table', data)
+}
+
+// 弹窗确定按钮功能
+async function dialogFormSubmit() {
+  // 验证表单数据
+  const isValid = await dialogFormRef.value.validate()
+  if (!isValid) {
+    console.log('表单数据验证失败')
+    return
+  }
+
+  // 封装数据
+  const formData = {
+    dialogTitle: dialogTitle.value,
+    cid: categoryId.value,
+    prices: {
+      lowest: dialogForm.prices.lowest,
+      highest: dialogForm.prices.highest,
+    },
+    starRating: dialogForm.starRating,
+    selectedBrands: dialogForm.dialogselectValue,
+    delivery: dialogForm.delivery,
+    isCount: dialogForm.isCount,
+    commodityCount: commodityCount.value,
+    selectedLabels: selectedLabels.value, // 如果需要选中的标签也发送到父组件
+  }
+
+  // 使用emit发送封装好的数据到父组件
+  emit('form-submitted', formData);
+  // emitter.emit('form-submitted', formData)
+
+  // 关闭弹窗
+  visible.value = false
+  resetDialogForm()
+}
+
+onMounted(() => {
+  setProductOrientationData()
+})
+</script>
+
+<style scoped>
+.custom-tree-node {
+  /* el-tree自定义样式 */
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
+}
+</style>

+ 70 - 8
src/views/adManage/sd/campaigns/CreateCampaigns/component/CustomTarget.vue

@@ -35,9 +35,10 @@
                   </el-select>
                 </div>
                 <el-tab-pane label="建议" name="advice">
+                  <div style="height: 450px"></div>
                 </el-tab-pane>
                 <el-tab-pane label="搜索" name="search">
-                  <CommodityOperate @add-to-table="handleAddToTable"></CommodityOperate>
+                  <BrowseSearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></BrowseSearch>
                 </el-tab-pane>
               </el-tabs>
             </el-tab-pane>
@@ -50,8 +51,12 @@
                     <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"> </el-tab-pane>
-                <el-tab-pane label="搜索" name="search"> </el-tab-pane>
+                <el-tab-pane label="建议" name="advice">
+                  <div style="height: 450px"></div>
+                </el-tab-pane>
+                <el-tab-pane label="搜索" name="search">
+                  <BuySearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></BuySearch>
+                </el-tab-pane>
               </el-tabs>
             </el-tab-pane>
           </el-tabs>
@@ -64,12 +69,19 @@
             </div>
             <el-button link type="danger" @click="handleDeleteAll" style="margin-right: 15px">删除所有</el-button>
           </div>
-          <el-table :data="addedTableData" :header-cell-style="changeTableHeader" height="649" style="width: 100%">
+          <el-table :data="addedTableData" :header-cell-style="changeTableHeader" height="600" style="width: 100%">
             <el-table-column prop="date" label="商品">
               <template #default="{ row }">
                 <div>浏览再营销</div>
                 <div>
-                  分类: <span style="color: black">{{ row.cna ? row.cna : '--' }}</span>
+                  分类: <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>
+                <div v-if="row.delivery">
+                  配送:
+                  <span style="color: black">{{ row.delivery ? (row.delivery === 'eligible' ? '具备Prime资格' : '不具备Prime资格') : '--' }}</span>
                 </div>
                 <div>
                   回溯期: <span style="color: black">{{ row.lookBack ? row.lookBack : '--' }}</span>
@@ -95,6 +107,9 @@
               </template>
             </el-table-column>
           </el-table>
+          <div style="display: flex; justify-content: space-around; padding-top: 15px">
+            <el-button type="primary" plain :disabled="!addedTableData.length" @click="handleSave">保存</el-button>
+          </div>
         </div>
       </div>
     </el-card>
@@ -104,8 +119,9 @@
 <script setup lang="ts">
 import { onMounted, ref, watch, reactive, CSSProperties } from 'vue'
 import { request } from '/@/utils/service'
-import CommodityOperate from './CommodityOperate.vue'
-import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
+import BrowseSearch from './BrowseSearch.vue'
+import BuySearch from './BuySearch.vue'
+import { ElMessage, type FormInstance, type FormRules, type TabsPaneContext } from 'element-plus'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { storeToRefs } from 'pinia'
 
@@ -232,6 +248,36 @@ function handleAddToTable(data) {
   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',
+    })
   }
 }
 
@@ -239,9 +285,25 @@ function handleAddToTable(data) {
 function handleDeleteAll() {
   addedTableData.value = []
 }
+
 // 操作列按钮功能
 function handleButtonClick(row) {
-  addedTableData.value = addedTableData.value.filter(item => item.cid !== row.cid)
+  addedTableData.value = addedTableData.value.filter((item) => item.cid !== row.cid)
+}
+
+// 点击保存触发
+function handleSave() {
+  createPromote()
+}
+
+async function createPromote() {
+  try {
+    const body = {}
+    const response = await xx()
+    console.log('response', response.data)
+  } catch (error) {
+
+  }
 }
 
 function changeTableHeader(row) {

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

@@ -1,10 +1,10 @@
 <template>
-  <div style="padding: 1px 0 2px 0;">
+  <div style="padding: 15px 0 2px 0;">
     <el-divider content-position="left">
       <span style="font-size: 18px; font-weight: 700">推广商品</span>
     </el-divider>
   </div>
-  <div prop="commodity" style="width: 100%" v-loading="productLoading">
+  <div prop="commodity" style="width: 100%; margin-bottom: 20px;" v-loading="productLoading">
     <div style="width: 100%; height: 620px; display: flex; border: 1px solid #e5e7ec; border-radius: 6px">
       <div style="width: 50%; border-right: 1px solid #e5e7ec">
         <el-tabs v-model="productTabs" class="demo-tabs">
@@ -141,7 +141,7 @@
                 <el-button type="danger" size="normal" link @click="delSelectedGoods">删除已选中</el-button>
               </template>
               <template #default="scope">
-                <el-button type="primary" size="small" @click="delSingleGoods(scope)" text>删除</el-button>
+                <el-button type="danger" size="small" @click="delSingleGoods(scope)" text>删除</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -162,6 +162,7 @@ import type { Ref } from 'vue'
 import { inject, onMounted, ref, watch } from 'vue'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { request } from '/@/utils/service'
+import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -182,8 +183,7 @@ const buttons = [{ type: 'primary', text: '添加' }] as const
 const productTabs = ref('first')
 const rightSelect = ref('latest')
 const respCampaignId = inject<Ref>('respCampaignId')
-const respCampaignName = inject<Ref>('respCampaignName')
-const respAdGroupId = inject<Ref>('respAdGroupId')
+const respAdGroupId = ref('')
 
 function setTableData(asin = '', sku = '') {
   return request({
@@ -215,7 +215,10 @@ function addSingleGoods(scope) {
   if (!isAlreadyAdded) {
     addedTableData.value.push(scope.row)
   } else {
-    console.log('Item is already added.')
+    ElMessage({
+      message: '请勿重复添加',
+      type: 'warning',
+    })
   }
 }
 
@@ -330,7 +333,7 @@ async function createAds() {
     }
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
     const resp = await request({
-      url: '/api/ad_manage/spads/create/',
+      url: '/api/ad_manage/sdads/create/',
       method: 'POST',
       data: filteredRequestData,
     })
@@ -376,6 +379,9 @@ const headerCellStyle = (args) => {
 }
 onMounted(() => {
   setTableData()
+  emitter.on('respAdGroupId', (value: any)=>{
+    respAdGroupId.value = value
+  })
 })
 </script>
 

+ 14 - 2
src/views/adManage/sd/campaigns/CreateCampaigns/index.vue

@@ -1,19 +1,31 @@
 <template>
   <div class="page-container">
-    <AdCampaign></AdCampaign>
+    <AdCampaign @send-campaign="getCampaign"></AdCampaign>
     <AdGroup></AdGroup>
     <component :is="currentComponent"></component>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, computed, onUnmounted } from 'vue'
+import { onMounted, ref, computed, onUnmounted, provide } 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'
 
+
+const respCampaignId = ref('')
+const respCampaignName = ref('')
+
+provide('respCampaignId', respCampaignId)
+provide('respCampaignName', respCampaignName)
+
+function getCampaign(data) {
+  respCampaignId.value = data.id
+  respCampaignName.value = data.name
+}
+
 // 获取targetType的值渲染自定义定向
 const targetType = ref()
 emitter.on('send-targetType', (value: any) => {

+ 1 - 2
src/views/adManage/sd/index.vue

@@ -10,8 +10,7 @@
           style="width: 400px"
           collapse-tags
           collapse-tags-tooltip
-          :max-collapse-tags="3"
-      >
+          :max-collapse-tags="3">
         <el-option v-for="info of portfolios" :label="info.name" :value="info.portfolioId"></el-option>
       </el-select>
     </div>

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

@@ -570,7 +570,7 @@
                       <el-tab-pane label="搜索" name="second">
                         <el-input placeholder="请输入关键词过滤" />
                         <el-scrollbar height="390px">
-                          <el-tree :data="searchClassifyTableData" :props="defaultProps">
+                          <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>