Browse Source

Merge branch 'wang' into test

WanGxC 1 year ago
parent
commit
70c1347c57

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

@@ -93,4 +93,18 @@ export function postAdGroup(body) {
       method: 'post',
       method: 'post',
       data: body,
       data: body,
   })
   })
+}
+export function getAudiencesList(query) {
+  return request({
+      url: '/api/ad_manage/sd/audienceslist/search/',
+      method: 'get',
+      params: query,
+  })
+}
+export function post(body) {
+  return request({
+      url: '/api/ad_manage/sdtargets/create/',
+      method: 'get',
+      data: body,
+  })
 }
 }

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

@@ -116,9 +116,9 @@ async function crateAdGroup() {
     const response = await postAdGroup(body)
     const response = await postAdGroup(body)
     if (response.data.adGroupId) {
     if (response.data.adGroupId) {
       respAdGroupId.value = response.data.adGroupId
       respAdGroupId.value = response.data.adGroupId
-      ElMessage({ message: '广告活动创建成功', type: 'success' })
+      ElMessage({ message: '广告创建成功', type: 'success' })
     } else {
     } else {
-      ElMessage.error('广告活动创建失败!')
+      ElMessage.error('广告创建失败!')
     }
     }
   } catch (error) {
   } catch (error) {
     console.log('error:', error)
     console.log('error:', error)

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

@@ -24,7 +24,7 @@
     <el-form :model="dialogForm" :rules="dialogRules" ref="dialogFormRef" style="margin-top: 20px">
     <el-form :model="dialogForm" :rules="dialogRules" ref="dialogFormRef" style="margin-top: 20px">
       <el-form-item style="padding-left: 140px">
       <el-form-item style="padding-left: 140px">
         <span style="margin-right: 10px; color: #616266; font-weight: 500">品牌</span>
         <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-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>

+ 296 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/CategorySearch.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>

+ 431 - 3
src/views/adManage/sd/campaigns/CreateCampaigns/component/ContentTarget.vue

@@ -5,16 +5,354 @@
         <span class="custom-card-icon">|</span>
         <span class="custom-card-icon">|</span>
         <span class="custom-card-Text">内容相关投放</span>
         <span class="custom-card-Text">内容相关投放</span>
       </div>
       </div>
+      <div class="main-container">
+        <div class="left-container">
+          <el-tabs v-model="topTabName" class="demo-tabs" @tab-click="handleClick">
+            <div class="tab-container-fixed-top">
+              <span class="tab-top-label">添加定向时的竞价设置: </span>
+              <el-select v-model="bidType">
+                <el-option v-for="item in bidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+              <el-input v-model="bid" :disabled="bidType == '1'" style="width: 200px">
+                <template #prepend>$</template>
+              </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-tab-pane label="建议" name="advice">
+                  <div style="height: 490px"></div>
+                </el-tab-pane>
+                <el-tab-pane label="搜索" name="search">
+                  <CategorySearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></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>
+                </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>
+                </el-tab-pane>
+              </el-tabs>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+        <!-- 右侧内容区 -->
+        <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-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.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>
+                <div v-if="row.delivery">
+                  配送:
+                  <span style="color: black">{{ row.delivery ? (row.delivery === 'eligible' ? '具备Prime资格' : '不具备Prime资格') : '--' }}</span>
+                </div>
+                <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>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="name" label="竞价" width="160">
+              <template #default="{ row }">
+                <el-input v-model.number="bid">
+                  <template #prepend>$</template>
+                </el-input>
+              </template>
+            </el-table-column>
+            <el-table-column prop="address" label="当前建议竞价" width="160">
+              <template #default="{ row }">
+                <div>$</div>
+                <div>$</div>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="60">
+              <template #default="{ row }">
+                <el-button link type="danger" size="small" @click="handleButtonClick(row)">删除</el-button>
+              </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>
     </el-card>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { onMounted } from 'vue'
+import { onMounted, ref, watch, reactive, CSSProperties } from 'vue'
+import { request } from '/@/utils/service'
+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'
+
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+
+// tab栏
+const topTabName = ref('category')
+
+function handleClick(tab: TabsPaneContext, event: Event) {
+  // console.log(tab, event)
+}
+
+// tab栏顶部固定部分功能
+const bidType = ref('2')
+const bidTypeOptions = [
+  {
+    value: '1',
+    label: '建议竞价',
+  },
+  {
+    value: '2',
+    label: '自定义竞价',
+  },
+]
+const bid = ref('0.75')
+
+watch(
+  bidType,
+  () => {
+    if (bidType.value == '1') {
+      bid.value = ''
+    } else {
+      bid.value = '0.75'
+    }
+  },
+  { immediate: true }
+)
 
 
-onMounted(() => {
+// 亚马逊受众tab的 下拉框和输入框
+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天',
+  },
+]
+
+// 购买再营销下的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',
+    })
+  }
+}
+
+// 获取单个商品的搜索数据
+// 获取单个商品的搜索数据
+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))
+
+  // 遍历所有选中的商品
+  selectedCommodities.forEach((commodity) => {
+    // 检查该商品是否已经存在于 addedTableData 中
+    const exists = addedTableData.value.some((item) => item.asin === commodity.asin && item.source === 'single')
+    if (!exists) {
+      // 如果不存在,则添加新商品到 addedTableData
+      addedTableData.value.push({
+        ...commodity,
+        source: 'single', // 添加标识
+        // 这里可以根据需要添加其他属性或转换数据
+      })
+    } else {
+      ElMessage({
+        message: `商品 ${commodity.asin} 已经添加,不能重复添加`,
+        type: 'warning',
+      })
+    }
+  })
+}
+
+
+// 删除所有Table数据
+function handleDeleteAll() {
+  addedTableData.value = []
+}
+
+// 操作列按钮功能
+function handleButtonClick(row) {
+  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) {
+  // console.log('error: ', error)
+  // }
+}
+
+function changeTableHeader(row) {
+  if (row) {
+    return {
+      background: '#f5f7fa',
+    }
+  }
+}
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
@@ -33,4 +371,94 @@ onMounted(() => {
   color: #306cd7;
   color: #306cd7;
   font-size: 26px;
   font-size: 26px;
 }
 }
+.main-container {
+  display: flex;
+  border: 1px solid #e5e7eb;
+  height: 700px;
+  margin-bottom: 20px;
+  border-radius: 6px;
+}
+.left-container {
+  width: 50%;
+  height: 699px;
+  border-right: 1px solid #e5e7eb;
+}
+.right-container {
+  width: 50%;
+  height: 699px;
+  border-bottom: 0;
+}
+.right-container-top {
+  height: 40px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.demo-tabs {
+  padding: 0 10px;
+}
+.demo-tabs > .el-tabs__content {
+  padding: 32px;
+  color: #6b778c;
+  font-size: 32px;
+  font-weight: 600;
+}
+/* 选中时的字体颜色 */
+::v-deep(.demo-tabs .el-tabs__item.is-top.is-active) {
+  color: #3569d6;
+}
+/* 选中时的下划线颜色 */
+::v-deep(.demo-tabs .el-tabs__active-bar.is-top) {
+  background-color: #3569d6;
+}
+.tab-container-fixed-top {
+  display: flex;
+  align-items: center;
+  margin-bottom: 4px;
+  gap: 8px;
+  font-size: 13px;
+  margin: 10px 0;
+}
+.tab-top-label {
+  color: #4e5969;
+  font-weight: 700;
+  margin-right: 10px;
+}
+.align-center-bottom {
+  align-items: center;
+  margin-bottom: 12px;
+}
+.tab-title {
+  line-height: 20px;
+  padding-bottom: 10px;
+  color: #8e9095;
+}
+.select-label {
+  padding-right: 5px;
+  color: #606266;
+}
+/* 修改隐藏table底部border */
+::v-deep(.el-table__inner-wrapper::before) {
+  background-color: #ffffff;
+}
+.double-line {
+  color: #1e2128;
+  font-weight: 500;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+.data-color {
+  color: rgb(30, 33, 41);
+}
+.img-box {
+  width: 60px;
+  height: 60px;
+  margin-top: 5px;
+  border: 1px solid rgb(194, 199, 207);
+  border-radius: 4px;
+}
 </style>
 </style>

+ 226 - 41
src/views/adManage/sd/campaigns/CreateCampaigns/component/CustomTarget.vue

@@ -18,11 +18,34 @@
               </el-input>
               </el-input>
             </div>
             </div>
             <el-tab-pane label="亚马逊受众" name="audience">
             <el-tab-pane label="亚马逊受众" name="audience">
-              <div class="el-row align-center-bottom" style="flex-wrap: nowrap">
-                <el-select v-model="audienceType" style="width: 140px; margin-right: 10px">
-                  <el-option v-for="item in audienceTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-                </el-select>
-                <el-input v-model="keywordInput" placeholder="请输入关键词进行过滤"></el-input>
+              <div>
+                <div style="display: flex">
+                  <el-select v-model="audienceType" @change="handleSearchChange" style="width: 140px; margin-right: 10px">
+                    <el-option v-for="item in audienceTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+                  </el-select>
+                  <el-input v-model="keywordInput" @change="handleSearchChange" placeholder="请输入关键词进行过滤"></el-input>
+                </div>
+                <hr style="margin-top: 5px" />
+                <el-table
+                  :data="searchData"
+                  :row-key="(item) => item.audienceId"
+                  :show-header="false"
+                  v-loading="searchLoading"
+                  height="550"
+                  style="width: 100%">
+                  <el-table-column prop="date" label="Date">
+                    <template #default="{ row }">
+                      <div style="font-weight: 500; color: #000">{{ row.category }}</div>
+                      <div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden">{{ row.audienceName }}</div>
+                      <div style="color: rgba(0, 0, 0, 0.45)">预估人数: {{ row.lowerBoundInclusive }} - {{ row.upperBoundExclusive }}</div>
+                    </template>
+                  </el-table-column>
+                  <el-table-column prop="opotion" label="option" width="80">
+                    <template #default="{ row }">
+                      <el-button type="primary" size="small" link @click="handleAddButtonClick(row)">添加</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
               </div>
               </div>
             </el-tab-pane>
             </el-tab-pane>
             <el-tab-pane label="浏览再营销" name="views">
             <el-tab-pane label="浏览再营销" name="views">
@@ -35,10 +58,23 @@
                   </el-select>
                   </el-select>
                 </div>
                 </div>
                 <el-tab-pane label="建议" name="advice">
                 <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>
                 <el-tab-pane label="搜索" name="search">
                 <el-tab-pane label="搜索" name="search">
-                  <BrowseSearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></BrowseSearch>
+                  <BrowseSearch @add-to-table="browseOrientation" @form-submitted="browseRefine"></BrowseSearch>
                 </el-tab-pane>
                 </el-tab-pane>
               </el-tabs>
               </el-tabs>
             </el-tab-pane>
             </el-tab-pane>
@@ -55,7 +91,7 @@
                   <div style="height: 450px"></div>
                   <div style="height: 450px"></div>
                 </el-tab-pane>
                 </el-tab-pane>
                 <el-tab-pane label="搜索" name="search">
                 <el-tab-pane label="搜索" name="search">
-                  <BuySearch @add-to-table="handleAddToTable" @form-submitted="handleFormSubmitted"></BuySearch>
+                  <BuySearch @add-to-table="buyOrientation" @form-submitted="buyRefine"></BuySearch>
                 </el-tab-pane>
                 </el-tab-pane>
               </el-tabs>
               </el-tabs>
             </el-tab-pane>
             </el-tab-pane>
@@ -72,25 +108,33 @@
           <el-table :data="addedTableData" :header-cell-style="changeTableHeader" height="600" style="width: 100%">
           <el-table :data="addedTableData" :header-cell-style="changeTableHeader" height="600" style="width: 100%">
             <el-table-column prop="date" label="商品">
             <el-table-column prop="date" label="商品">
               <template #default="{ row }">
               <template #default="{ row }">
-                <div>浏览再营销</div>
-                <div>
+                <div v-if="row.cna || row.dialogTitle">浏览再营销</div>
+                <div v-if="row.cna || row.dialogTitle">
                   分类: <span style="color: black">{{ row.cna ? row.cna : row.dialogTitle }}</span>
                   分类: <span style="color: black">{{ row.cna ? row.cna : row.dialogTitle }}</span>
                 </div>
                 </div>
-                <div v-if="row.prices">
-                  商品价格: <span style="color: black">{{ row.prices ? row.prices : '--' }}</span>
+                <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>
                 </div>
                 </div>
                 <div v-if="row.delivery">
                 <div v-if="row.delivery">
                   配送:
                   配送:
-                  <span style="color: black">{{ row.delivery ? (row.delivery === 'eligible' ? '具备Prime资格' : '不具备Prime资格') : '--' }}</span>
+                  <span style="color: black">{{ row.delivery ? row.delivery: '--' }}</span>
                 </div>
                 </div>
-                <div>
-                  回溯期: <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.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>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
             <el-table-column prop="name" label="竞价" width="160">
             <el-table-column prop="name" label="竞价" width="160">
               <template #default="{ row }">
               <template #default="{ row }">
-                <el-input v-model.number="bid">
+                <el-input v-model.number="row.bid">
                   <template #prepend>$</template>
                   <template #prepend>$</template>
                 </el-input>
                 </el-input>
               </template>
               </template>
@@ -122,6 +166,7 @@ import { request } from '/@/utils/service'
 import BrowseSearch from './BrowseSearch.vue'
 import BrowseSearch from './BrowseSearch.vue'
 import BuySearch from './BuySearch.vue'
 import BuySearch from './BuySearch.vue'
 import { ElMessage, type FormInstance, type FormRules, type TabsPaneContext } from 'element-plus'
 import { ElMessage, type FormInstance, type FormRules, type TabsPaneContext } from 'element-plus'
+import { getAudiencesList } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { storeToRefs } from 'pinia'
 import { storeToRefs } from 'pinia'
 
 
@@ -162,26 +207,26 @@ watch(
 )
 )
 
 
 // 亚马逊受众tab的 下拉框和输入框
 // 亚马逊受众tab的 下拉框和输入框
-const audienceType = ref('1')
+const audienceType = ref('')
 const audienceTypeOptions = [
 const audienceTypeOptions = [
   {
   {
-    value: '1',
+    value: '',
     label: '所有受众',
     label: '所有受众',
   },
   },
   {
   {
-    value: '2',
+    value: 'Lifestyle',
     label: '生活方式',
     label: '生活方式',
   },
   },
   {
   {
-    value: '3',
+    value: 'Interest',
     label: '兴趣',
     label: '兴趣',
   },
   },
   {
   {
-    value: '4',
+    value: 'Life event',
     label: '生活事件',
     label: '生活事件',
   },
   },
   {
   {
-    value: '5',
+    value: 'In-market',
     label: '场内客群',
     label: '场内客群',
   },
   },
 ]
 ]
@@ -243,11 +288,35 @@ const purchasesLookBackOptions = [
 // 已添加的表格数据
 // 已添加的表格数据
 const addedTableData = ref([])
 const addedTableData = ref([])
 
 
-function handleAddToTable(data) {
-  // 检查该数据是否已经存在于 addedTableData 中
+function browseOrientation(data) {
+  const exists = addedTableData.value.some((item) => item.cid === data.cid)
+  if (!exists) {
+    const dataWithLookback = {
+      type: 'c',
+      tactictype: 'views',
+      lookback: viewsLookBack.value,
+      bid: bid.value,
+      classificationId: data.cid
+    }
+    console.log('dataWithLookback', dataWithLookback)
+    addedTableData.value.push(dataWithLookback)
+  } else {
+    ElMessage({
+      message: '请勿重复添加',
+      type: 'warning',
+    })
+  }
+}
+
+function buyOrientation(data) {
   const exists = addedTableData.value.some((item) => item.cid === data.cid)
   const exists = addedTableData.value.some((item) => item.cid === data.cid)
   if (!exists) {
   if (!exists) {
-    addedTableData.value.push(data)
+    const dataWithLookback = {
+      ...data,
+      lookback: viewsLookBack.value,
+    }
+    console.log('dataWithLookback', dataWithLookback)
+    addedTableData.value.push(dataWithLookback)
   } else {
   } else {
     ElMessage({
     ElMessage({
       message: '请勿重复添加',
       message: '请勿重复添加',
@@ -257,22 +326,63 @@ function handleAddToTable(data) {
 }
 }
 
 
 // 点击细化后弹窗确认触发
 // 点击细化后弹窗确认触发
-function handleFormSubmitted(data) {
+const deliveryMap = {
+  eligible: '具备Prime资格',
+  diseligible: '不具备Prime资格',
+  all: '所有',
+}
+
+function browseRefine(data) {
+  const exists = addedTableData.value.some((item) => item.cid === data.cid)
+
+  if (!exists) {
+    console.log('data', data)
+    const tableRow = {
+      type: 'c',
+      tactictype: 'views',
+      lookback: viewsLookBack.value,
+      bid: bid.value,
+      brandId: data.selectedBrands,
+      classificationId: data.cid,
+      delivery: data.delivery,
+      // categoryId: data.categoryId,
+      dialogTitle: data.dialogTitle, // 分类名称
+      low_prices: data.prices.lowest,
+      high_prices: data.prices.highest,
+      low_rating: data.starRating[0],
+      high_rating: data.starRating[1],
+    }
+    addedTableData.value.push(tableRow) // 添加到表格数据
+    console.log('tableRow', tableRow)
+  } else {
+    ElMessage({
+      message: '请勿重复添加',
+      type: 'warning',
+    })
+  }
+}
+
+function buyRefine(data) {
   const exists = addedTableData.value.some((item) => item.cid === data.cid)
   const exists = addedTableData.value.some((item) => item.cid === data.cid)
 
 
   if (!exists) {
   if (!exists) {
     // addedTableData.value.push(data)
     // addedTableData.value.push(data)
-    const deliveryText = data.delivery === 'eligible' ? '具备Prime资格' : data.delivery === 'diseligible' ? '不具备Prime资格' : '所有'
+    const deliveryText = deliveryMap[data.delivery]
 
 
     const tableRow = {
     const tableRow = {
+      type: 'c',
+      tactictype: 'views',
+      lookback: viewsLookBack.value,
+      bid: bid.value,
+      brandId: data.brandId,
+      classificationId: data.cid,
+      delivery: data.delivery,
       categoryId: data.categoryId,
       categoryId: data.categoryId,
       dialogTitle: data.dialogTitle, // 分类名称
       dialogTitle: data.dialogTitle, // 分类名称
       prices: `\$${data.prices.lowest} ~ \$${data.prices.highest}`, // 价格范围
       prices: `\$${data.prices.lowest} ~ \$${data.prices.highest}`, // 价格范围
-      delivery: deliveryText, // 配送选项
       // 其他需要的字段...
       // 其他需要的字段...
     }
     }
     addedTableData.value.push(tableRow) // 添加到表格数据
     addedTableData.value.push(tableRow) // 添加到表格数据
-    console.log('data', data)
   } else {
   } else {
     ElMessage({
     ElMessage({
       message: '请勿重复添加',
       message: '请勿重复添加',
@@ -281,29 +391,104 @@ function handleFormSubmitted(data) {
   }
   }
 }
 }
 
 
+// 获取搜索的数据
+const searchLoading = ref(false)
+const searchData = ref([])
+const categoryMap = {
+  Lifestyle: '生活方式',
+  'In-market': '场内客群',
+  Interest: '兴趣',
+  'Life event': '生活事件',
+}
+
+function handleSearchChange() {
+  getCustomData()
+}
+
+async function getCustomData() {
+  searchLoading.value = true
+  const query = {
+    profile_id: profile.value.profile_id,
+    categoryvalues: audienceType.value,
+    values: keywordInput.value,
+  }
+  try {
+    const response = await getAudiencesList(query)
+    searchData.value = response.data.audiences.map((audience) => ({
+      ...audience,
+      category: categoryMap[audience.category] || audience.category,
+    }))
+  } catch (error) {
+    console.error('error: ', error)
+  } finally {
+    searchLoading.value = false
+  }
+}
+
+function handleAddButtonClick(row) {
+  // 检查该行数据是否已经存在于 addedTableData 中
+  const exists = addedTableData.value.some((item) => item.audienceId === row.audienceId)
+  if (!exists) {
+    // 如果不存在,则添加它到 addedTableData
+    addedTableData.value.push(row)
+  } else {
+    // 如果已存在,显示一个警告消息
+    ElMessage({
+      message: `选项 ${row.audienceName} 已经添加,不能重复添加`,
+      type: 'warning',
+    })
+  }
+}
+
 // 删除所有Table数据
 // 删除所有Table数据
 function handleDeleteAll() {
 function handleDeleteAll() {
   addedTableData.value = []
   addedTableData.value = []
 }
 }
 
 
-// 操作列按钮功能
+// 单独删除功能
 function handleButtonClick(row) {
 function handleButtonClick(row) {
-  addedTableData.value = addedTableData.value.filter((item) => item.cid !== row.cid)
+  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 handleSave() {
-  createPromote()
+function addPromoteProduct() {
+  const newProduct = {
+    type: 'p',
+    tactictype: 'purchases',
+    lookback: viewsLookBack.value,
+    asin: '',
+    bid: 0.2,
+    productType: 'relatedProduct',
+  }
+  // 例如 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',
+    })
+  }
 }
 }
 
 
-async function createPromote() {
-  try {
-    const body = {}
-    const response = await xx()
-    console.log('response', response.data)
-  } catch (error) {
+function addPromoteSimilar() {}
 
 
-  }
+// 点击保存触发
+function handleSave() {
+  // createCustom()
+  console.log('addedTableData:', addedTableData.value)
 }
 }
 
 
 function changeTableHeader(row) {
 function changeTableHeader(row) {

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

@@ -147,7 +147,7 @@
           </el-table>
           </el-table>
         </div>
         </div>
         <div style="display: flex; justify-content: space-around; padding-top: 5px">
         <div style="display: flex; justify-content: space-around; padding-top: 5px">
-          <el-button type="primary" plain :disabled="addedTableData.length == 0" @click="submitProductForm">保存</el-button>
+          <el-button type="primary" plain :disabled="!addedTableData.length || !respAdGroupId" @click="submitProductForm">保存</el-button>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
@@ -282,14 +282,17 @@ function selChange(e) {
     setTableData('', value)
     setTableData('', value)
   }
   }
 }
 }
+
 // 点击表格选项触发事件
 // 点击表格选项触发事件
 function handleSelectionChange(selection) {
 function handleSelectionChange(selection) {
   selections = selection
   selections = selection
 }
 }
+
 // 获取addedTable中已选中的项
 // 获取addedTable中已选中的项
 function handleAddedGoodsChange(selection) {
 function handleAddedGoodsChange(selection) {
   addedSels = selection
   addedSels = selection
 }
 }
+
 // 添加已选中的项
 // 添加已选中的项
 function handleGoodsAdd() {
 function handleGoodsAdd() {
   // 过滤掉已经存在于addedData.value中的项
   // 过滤掉已经存在于addedData.value中的项
@@ -301,6 +304,7 @@ function handleGoodsAdd() {
     addedTableData.value.push(...newSelections)
     addedTableData.value.push(...newSelections)
   }
   }
 }
 }
+
 // 点击Tab
 // 点击Tab
 const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
 const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
   console.log(tab, event)
   console.log(tab, event)
@@ -309,13 +313,17 @@ const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
 function isItemInList(item, list) {
 function isItemInList(item, list) {
   return list.some((listItem) => listItem.sku === item.sku && listItem.asin === item.asin)
   return list.some((listItem) => listItem.sku === item.sku && listItem.asin === item.asin)
 }
 }
+
 // 监听商品右侧表格已添加的数据并转化数据格式
 // 监听商品右侧表格已添加的数据并转化数据格式
+let firstAsin = ''
 watch(
 watch(
   addedTableData,
   addedTableData,
   (newValue, oldValue) => {
   (newValue, oldValue) => {
     newValue.forEach((item) => {
     newValue.forEach((item) => {
       if (!isItemInList(item, addedAdsTableItems.value)) {
       if (!isItemInList(item, addedAdsTableItems.value)) {
         addedAdsTableItems.value.push({ sku: item.sku, asin: item.asin })
         addedAdsTableItems.value.push({ sku: item.sku, asin: item.asin })
+        firstAsin = addedAdsTableItems.value[0].asin  // 后续需要用到
+        emitter.emit('send-firstAsin', firstAsin)
       }
       }
     })
     })
   },
   },
@@ -329,7 +337,7 @@ async function createAds() {
       campaignId: respCampaignId.value,
       campaignId: respCampaignId.value,
       adGroupId: respAdGroupId.value,
       adGroupId: respAdGroupId.value,
       asinsku: addedAdsTableItems.value,
       asinsku: addedAdsTableItems.value,
-      state: 'PAUSED',
+      state: 'paused',
     }
     }
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
     const resp = await request({
     const resp = await request({
@@ -337,9 +345,8 @@ async function createAds() {
       method: 'POST',
       method: 'POST',
       data: filteredRequestData,
       data: filteredRequestData,
     })
     })
-    console.log('🚀 ~ createAds ~ resp-->>', resp)
     productLoading.value = false
     productLoading.value = false
-    if (resp.data.success.length > 0) {
+    if (resp.data[0].adId) {
       addedTableData.value = []
       addedTableData.value = []
       ElMessage({
       ElMessage({
         message: '商品创建成功',
         message: '商品创建成功',

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

@@ -0,0 +1,109 @@
+<template>
+  <div style="height: 490px" v-loading="containerLoading">
+    <el-input v-model="searchInput" @change="handleChange" placeholder="按ASIN搜索"></el-input>
+    <hr style="margin-top: 5px" />
+    <el-table :data="commodityData" :show-header="false" @selection-change="handleSelectionChange" height="450" style="width: 100%">
+      <el-table-column prop="date" label="商品">
+        <template #default="scope">
+          <div style="display: flex; align-items: center">
+            <div style="margin-right: 8px; line-height: normal">
+              <el-image class="img-box" :src="scope.row.image_link" />
+            </div>
+            <div>
+              <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
+                <div class="double-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
+              </el-tooltip>
+              <span
+                >ASIN:
+                <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
+              </span>
+            </div>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="name" label="Name" width="80">
+        <template #default="scope"> </template>
+      </el-table-column>
+      <el-table-column type="selection" width="55" />
+    </el-table>
+  </div>
+</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 { getCommodityCard } from '../api/index'
+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 searchInput = ref('')
+const commodityData: any = ref('')
+
+function handleChange() {
+  searchAsin()
+}
+
+async function searchAsin() {
+  containerLoading.value = true
+  try {
+    const query = {
+      profile_id: profile.value.profile_id,
+      asin: searchInput.value,
+    }
+    const response = await getCommodityCard(query)
+    commodityData.value = response.data
+    console.log('commodityData.value', commodityData.value)
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    containerLoading.value = false
+  }
+}
+
+// 暴露数据给父组件
+const emit = defineEmits(['updateSelected'])
+
+function handleSelectionChange(selection) {
+  // selection 是所有被选中的行数据
+  emit('updateSelected', selection)
+}
+
+</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;
+}
+.double-line {
+  color: #1e2128;
+  font-weight: 500;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+.data-color {
+  color: rgb(30, 33, 41);
+}
+.img-box {
+  width: 60px;
+  height: 60px;
+  margin-top: 5px;
+  border: 1px solid rgb(194, 199, 207);
+  border-radius: 4px;
+}
+</style>

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

@@ -463,7 +463,7 @@
                   </div>
                   </div>
                 </el-card>
                 </el-card>
                 <div style="display: flex; justify-content: space-around; padding-top: 0px">
                 <div style="display: flex; justify-content: space-around; padding-top: 0px">
-                  <el-button type="primary" plain>保存</el-button>
+                  <el-button type="primary" plain @click="keyWordsSave" :disabled="!addedKeyWordsTableData.length">保存</el-button>
                 </div>
                 </div>
               </div>
               </div>
             </div>
             </div>
@@ -539,17 +539,14 @@
                   style="border: 0; border-right: 0; border-bottom-left-radius: 6px; border-top-left-radius: 5px; overflow: hidden">
                   style="border: 0; border-right: 0; border-bottom-left-radius: 6px; border-top-left-radius: 5px; overflow: hidden">
                   <el-tab-pane label="品类" style="border-top-left-radius: 6px">
                   <el-tab-pane label="品类" style="border-top-left-radius: 6px">
                     <div style="display: flex; align-items: center">
                     <div style="display: flex; align-items: center">
-                    <span style="width: 40px">竞价:</span>
-                    <el-select v-model="categoryBiddingType" @change="singleGoodsBidSelectChanged" class="m-2" placeholder="Select">
-                      <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-                    </el-select>
-                    <el-input 
-                      v-model="singleGoodsBidInput" 
-                      :disabled="categoryBiddingType === 'defaultBid'" 
-                      style="width: 200px">
-                      <template #prepend>$</template>
-                    </el-input>
-                  </div>
+                      <span style="width: 40px">竞价:</span>
+                      <el-select v-model="categoryBiddingType" @change="singleGoodsBidSelectChanged" class="m-2" placeholder="Select">
+                        <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+                      </el-select>
+                      <el-input v-model="singleGoodsBidInput" :disabled="categoryBiddingType === 'defaultBid'" style="width: 200px">
+                        <template #prepend>$</template>
+                      </el-input>
+                    </div>
 
 
                     <el-tabs v-model="categoryTabs" class="category-tabs">
                     <el-tabs v-model="categoryTabs" class="category-tabs">
                       <el-tab-pane label="建议" name="first">
                       <el-tab-pane label="建议" name="first">
@@ -597,8 +594,7 @@
                                 @change="dialogSelectChange"
                                 @change="dialogSelectChange"
                                 multiple
                                 multiple
                                 placeholder="请选择"
                                 placeholder="请选择"
-                                :loading="dialogSelectLoading"
-                                >
+                                :loading="dialogSelectLoading">
                                 <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
                                 <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
                               </el-select>
                               </el-select>
                             </el-form-item>
                             </el-form-item>
@@ -643,10 +639,7 @@
                       <el-select class="m-2" v-model="singleGoodsBidSelect" @change="singleGoodsBidSelectChanged">
                       <el-select class="m-2" v-model="singleGoodsBidSelect" @change="singleGoodsBidSelectChanged">
                         <el-option v-for="item in singleGoodsBidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
                         <el-option v-for="item in singleGoodsBidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
                       </el-select>
                       </el-select>
-                      <el-input
-                        v-model="singleGoodsBidInput"
-                        :disabled="singleGoodsBidSelect == 'defaultBid'"
-                        style="width: 200px">
+                      <el-input v-model="singleGoodsBidInput" :disabled="singleGoodsBidSelect == 'defaultBid'" style="width: 200px">
                         <template #prepend>$</template>
                         <template #prepend>$</template>
                       </el-input>
                       </el-input>
                       <div style="margin-left: 20px">
                       <div style="margin-left: 20px">
@@ -979,6 +972,7 @@ const campaignRules = computed(() => ({
 }))
 }))
 
 
 async function createCampaigns() {
 async function createCampaigns() {
+  const previousRespCampaignId = respCampaignId.value
   try {
   try {
     // 必需字段列表
     // 必需字段列表
     const requiredFields = [
     const requiredFields = [
@@ -1021,9 +1015,10 @@ async function createCampaigns() {
       method: 'POST',
       method: 'POST',
       data: filteredRequestData,
       data: filteredRequestData,
     })
     })
-    console.log('🚀 ~ createCampaigns ~ resp-->>', resp)
-    respCampaignId.value = resp.data.campaignId
-    if (respCampaignId.value) {
+
+    if (resp.data && resp.data.campaignId) {
+      // 确保 resp.data 和 resp.data.campaignId 存在
+      respCampaignId.value = resp.data.campaignId // 更新 respCampaignId
       adGroupSave.value = false
       adGroupSave.value = false
       campaignLoading.value = false
       campaignLoading.value = false
       ElMessage({
       ElMessage({
@@ -1033,9 +1028,13 @@ async function createCampaigns() {
     } else {
     } else {
       campaignLoading.value = false
       campaignLoading.value = false
       ElMessage.error('广告活动创建失败!')
       ElMessage.error('广告活动创建失败!')
+      // 如果创建失败,恢复到之前的 respCampaignId
+      respCampaignId.value = previousRespCampaignId
     }
     }
   } catch (error) {
   } catch (error) {
     console.error('请求失败:', error)
     console.error('请求失败:', error)
+    // 如果请求失败,也恢复到之前的 respCampaignId
+    respCampaignId.value = previousRespCampaignId
   }
   }
 }
 }
 
 
@@ -1726,29 +1725,29 @@ function singleGoodsSearchChaneged() {
   setSearchTableData()
   setSearchTableData()
 }
 }
 function addSingleSearch(scope) {
 function addSingleSearch(scope) {
-  console.log('🚀 ~ addSingleSearch ~ scope-->>', scope);
+  console.log('🚀 ~ addSingleSearch ~ scope-->>', scope)
 
 
-  const typesToAdd = [];
+  const typesToAdd = []
   if (expand.value) {
   if (expand.value) {
-    typesToAdd.push('ASIN_EXPANDED_FROM');
+    typesToAdd.push('ASIN_EXPANDED_FROM')
   }
   }
   if (accurate.value) {
   if (accurate.value) {
-    typesToAdd.push('ASIN_SAME_AS');
+    typesToAdd.push('ASIN_SAME_AS')
   }
   }
   const productTypeMap = {
   const productTypeMap = {
     ASIN_EXPANDED_FROM: '扩展',
     ASIN_EXPANDED_FROM: '扩展',
     ASIN_SAME_AS: '精确',
     ASIN_SAME_AS: '精确',
-  };
+  }
 
 
   typesToAdd.forEach((productType) => {
   typesToAdd.forEach((productType) => {
-    const isAlreadyAdded = productOrientationTableData.value.some(item => item.sku === scope.row.sku && item.productType === productType);
+    const isAlreadyAdded = productOrientationTableData.value.some((item) => item.sku === scope.row.sku && item.productType === productType)
 
 
-    let bidValue = null;
+    let bidValue = null
     // 根据 categoryBiddingType.value 的值设置 bidValue
     // 根据 categoryBiddingType.value 的值设置 bidValue
     if (categoryBiddingType.value === 'defaultBid') {
     if (categoryBiddingType.value === 'defaultBid') {
-      bidValue = adGroupRuleForm.defaultBidInp;
+      bidValue = adGroupRuleForm.defaultBidInp
     } else if (categoryBiddingType.value === 'customBid') {
     } else if (categoryBiddingType.value === 'customBid') {
-      bidValue = singleGoodsBidInput.value;
+      bidValue = singleGoodsBidInput.value
     }
     }
 
 
     if (!isAlreadyAdded) {
     if (!isAlreadyAdded) {
@@ -1759,15 +1758,14 @@ function addSingleSearch(scope) {
         productType: productType,
         productType: productType,
         productTypeText: productTypeMap[productType],
         productTypeText: productTypeMap[productType],
         bid: bidValue, // 添加 bid 值
         bid: bidValue, // 添加 bid 值
-      };
-      productOrientationTableData.value.push(newData);
+      }
+      productOrientationTableData.value.push(newData)
     } else {
     } else {
-      console.log(`${productType} item is already added.`);
+      console.log(`${productType} item is already added.`)
     }
     }
-  });
+  })
 }
 }
 
 
-
 let selectedLabels = ref([]) // 选中的label数组
 let selectedLabels = ref([]) // 选中的label数组
 function dialogSelectChange(event) {
 function dialogSelectChange(event) {
   console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
   console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
@@ -1813,12 +1811,12 @@ function dialogFormSubmit() {
         diseligible: '不具备Prime资格',
         diseligible: '不具备Prime资格',
       }
       }
 
 
-      let bidValue = null;
+      let bidValue = null
       // 根据 categoryBiddingType.value 的值设置 bidValue
       // 根据 categoryBiddingType.value 的值设置 bidValue
       if (categoryBiddingType.value === 'defaultBid') {
       if (categoryBiddingType.value === 'defaultBid') {
-        bidValue = adGroupRuleForm.defaultBidInp;
+        bidValue = adGroupRuleForm.defaultBidInp
       } else if (categoryBiddingType.value === 'customBid') {
       } else if (categoryBiddingType.value === 'customBid') {
-        bidValue = singleGoodsBidInput.value;
+        bidValue = singleGoodsBidInput.value
       }
       }
 
 
       selectedLabels.value.forEach((brandLabel) => {
       selectedLabels.value.forEach((brandLabel) => {
@@ -1851,15 +1849,15 @@ function dialogFormSubmit() {
 
 
 // 定向按钮功能
 // 定向按钮功能
 function orientate(node, data) {
 function orientate(node, data) {
-  console.log('🚀 ~ orientate ~ data-->>', data);
-  const exists = productOrientationTableData.value.some(item => item.cid === data.cid);
+  console.log('🚀 ~ orientate ~ data-->>', data)
+  const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
 
 
-  let bidValue = null;
+  let bidValue = null
   // 根据 categoryBiddingType.value 的值设置 bidValue
   // 根据 categoryBiddingType.value 的值设置 bidValue
   if (categoryBiddingType.value === 'defaultBid') {
   if (categoryBiddingType.value === 'defaultBid') {
-    bidValue = adGroupRuleForm.defaultBidInp;
+    bidValue = adGroupRuleForm.defaultBidInp
   } else if (categoryBiddingType.value === 'customBid') {
   } else if (categoryBiddingType.value === 'customBid') {
-    bidValue = singleGoodsBidInput.value;
+    bidValue = singleGoodsBidInput.value
   }
   }
 
 
   if (!exists) {
   if (!exists) {
@@ -1868,8 +1866,8 @@ function orientate(node, data) {
       classification: data.cna,
       classification: data.cna,
       classificationId: data.cid,
       classificationId: data.cid,
       bid: bidValue, // 将 bid 值添加到新数据中
       bid: bidValue, // 将 bid 值添加到新数据中
-    };
-    productOrientationTableData.value.push(newData);
+    }
+    productOrientationTableData.value.push(newData)
   }
   }
 }
 }
 
 
@@ -1877,7 +1875,7 @@ let productTargetBidList = ref([])
 async function productTagetSave() {
 async function productTagetSave() {
   console.log('tableData', productOrientationTableData.value)
   console.log('tableData', productOrientationTableData.value)
   // 检查是否存在 bid 为空的行
   // 检查是否存在 bid 为空的行
-  const hasEmptyBid = productOrientationTableData.value.some(row => row.bid == null || row.bid === '')
+  const hasEmptyBid = productOrientationTableData.value.some((row) => row.bid == null || row.bid === '')
   // 直接返回,不继续执行
   // 直接返回,不继续执行
   if (hasEmptyBid) {
   if (hasEmptyBid) {
     console.log('存在空的 bid,不发送请求')
     console.log('存在空的 bid,不发送请求')
@@ -1895,7 +1893,7 @@ async function productTagetSave() {
       adGroupId: respAdGroupId.value,
       adGroupId: respAdGroupId.value,
       campaignId: respCampaignId.value,
       campaignId: respCampaignId.value,
       expressionList: productOrientationTableData.value,
       expressionList: productOrientationTableData.value,
-      state: "PAUSED"
+      state: 'PAUSED',
     }
     }
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
     const resp = await request({
     const resp = await request({
@@ -1947,18 +1945,56 @@ const keyWordsTableData = ref([]) // 关键词定向左侧表格数据
 const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
 const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
 const keyWordsTextarea = ref('')
 const keyWordsTextarea = ref('')
 
 
+function addKeyWords() {
+  const trimmedText = keyWordsTextarea.value.trim()
+  const items = trimmedText.split(/,|\n/)
+
+  items.forEach((item) => {
+    const trimmedItem = item.trim()
+    if (trimmedItem) {
+      let keyWordEntry = {
+        keyword: trimmedItem,
+        matchType: '广泛', // 或者其他匹配类型,根据你的具体需求来设置
+        bid: bidInput.value, // 使用用户输入的出价或默认出价
+      }
+
+      if (!addedKeyWordsTableData.value.some((n) => n.keyword === keyWordEntry.keyword)) {
+        addedKeyWordsTableData.value.push(keyWordEntry)
+      } else {
+        console.log('关键词已存在,未被添加到列表中')
+      }
+    } else {
+      console.log('有空项目,未被添加到列表中')
+    }
+  })
+  keyWordsTextarea.value = ''
+}
+
+function delSingleKeyWord(scope) {
+  const index = addedKeyWordsTableData.value.findIndex((item) => item.keyword === scope.row.keyword)
+  if (index !== -1) {
+    addedKeyWordsTableData.value.splice(index, 1)
+  } else {
+    console.log('无效的索引,无法删除条目')
+  }
+}
+
 function delAllKeyWords() {
 function delAllKeyWords() {
-  // 删除已经添加的关键词
   addedKeyWordsTableData.value = []
   addedKeyWordsTableData.value = []
 }
 }
 
 
-function addKeyWords() {
-  // 添加关键词
-  if (keyWordsTextarea.value) {
-    addedKeyWordsTableData.value.push({
-      keyWords: keyWordsTextarea.value,
-    })
-    keyWordsTextarea.value = ''
+async function keyWordsSave() {
+  // 假设你需要保存这些关键词到服务器
+  try {
+    const requestData = {
+      // ...其他需要的数据...
+      keywords: addedKeyWordsTableData.value,
+    }
+    // 发送请求...
+    console.log('请求发送成功,关键词已保存', requestData)
+    delAllKeyWords()
+  } catch (error) {
+    console.error('请求失败:', error)
   }
   }
 }
 }
 
 
@@ -1993,10 +2029,7 @@ function addNegative() {
       console.log('有空项目,未被添加到列表中')
       console.log('有空项目,未被添加到列表中')
     }
     }
   })
   })
-
   negativeWordsTextarea.value = ''
   negativeWordsTextarea.value = ''
-  console.log('🚀 ~ exactNegativeList-->>', exactNegativeList)
-  console.log('🚀 ~ phraseNegativeList-->>', phraseNegativeList)
 }
 }
 
 
 function delAllNegative() {
 function delAllNegative() {
@@ -2057,9 +2090,8 @@ async function negativeWordsSave() {
       method: 'POST',
       method: 'POST',
       data: filteredRequestData,
       data: filteredRequestData,
     })
     })
-    console.log('🚀 ~ negativeWordsSave ~ resp-->>', resp)
     negativeWordsLoading.value = false
     negativeWordsLoading.value = false
-    if (resp.data.negativeKeyworderror.length !== 0) {
+    if (resp.data.negativeKeywordsuccess.length !== 0) {
       ElMessage({
       ElMessage({
         message: '否定词创建成功',
         message: '否定词创建成功',
         type: 'success',
         type: 'success',