Browse Source

✨ feat: 新增上传图片和视频接口

WanGxC 1 năm trước cách đây
mục cha
commit
5113447d07

+ 1 - 1
.env

@@ -3,6 +3,6 @@ VITE_PORT = 8080
 
 # open 运行 npm run dev 时自动打开浏览器
 VITE_OPEN = false
-
+BROWSER = Google Chrome.app
 # public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
 VITE_PUBLIC_PATH = /web

+ 40 - 1
src/views/adManage/sb/campaigns/CreateCampaigns/api/index.ts

@@ -112,4 +112,43 @@ export function checkAsset(obj) {
       method: 'post',
       data: obj,
   })
-}
+}
+
+export function getDefaultSpotlightAsin(query) {
+  return request({
+      url: '/api/ad_manage/sb/defaultspotlightasin/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getSellerInStock(obj) {
+  return request({
+      url: '/api/sellers/listings/sellerinstock/',
+      method: 'post',
+      data: obj
+  })
+}
+export function postStoreSpotlight(obj) {
+  return request({
+      url: '/api/ad_manage/sbads/storespotlight/create/',
+      method: 'post',
+      data: obj
+  })
+}
+
+export function postBrandVideo(obj) {
+  return request({
+      url: '/api/ad_manage/sbads/brandvideo/create/',
+      method: 'post',
+      data: obj
+  })
+}
+export function postVideo(obj) {
+  return request({
+      url: '/api/ad_manage/sbads/video/create/',
+      method: 'post',
+      data: obj
+  })
+}
+

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

@@ -249,7 +249,7 @@ async function createCampaigns() {
     smartDefault: 'MANUAL',
     costType: 'CPC',
     goal: 'PAGE_VISIT',
-    state: 'ENABLED',
+    state: 'PAUSED',
     ...(campaignRuleForm.firstPage && { h_percentage: campaignRuleForm.firstPage }),
     ...(campaignRuleForm.commodityPage && { d_percentage: campaignRuleForm.commodityPage }),
     ...(campaignRuleForm.otherPlace && { o_percentage: campaignRuleForm.otherPlace }),

+ 39 - 1
src/views/adManage/sb/campaigns/CreateCampaigns/component/AdFormat.vue

@@ -139,7 +139,6 @@ import emitter from '/@/utils/emitter'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
 
-
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
 
@@ -196,14 +195,53 @@ async function getShopOptions() {
   }
 }
 
+watch(
+  () => ruleForm.shop,
+  () => {
+    setTimeout(() => {
+      emitter.emit('video-shop', focusShopOptions.value[0])
+    }, 2000)
+  },
+  { deep: true }
+)
+
+watch(
+  () => flagshipStoreRuleForm2.focusShop,
+  () => {
+    setTimeout(() => {
+      emitter.emit('spotlight-shop', focusShopOptions.value[0])
+    }, 2000)
+  }
+)
+
 async function getPageOptions() {
   try {
     const response = await getStoreurl({ profile_id: profile.value.profile_id })
     pageOptions.value = response.data.storePageInfo
+    // console.log('pageOptions:', pageOptions.value)
   } catch (error) {
     console.log('error:', error)
   }
 }
+let selectedPage: any = ''
+
+watch(
+  () => ruleForm.page,
+  (newPageValue) => {
+    // Find the selected page from pageOptions
+    selectedPage = pageOptions.value.find((page) => page.storePageUrl === newPageValue)
+    if (selectedPage) {
+      // Print the storePageUrl of the selected page
+      // console.log(`Selected page URL: ${selectedPage.storePageUrl}`)
+      setTimeout(() => {
+        emitter.emit('page', selectedPage.storePageUrl)
+      }, 2000);
+      
+    } else {
+      console.log('No page selected or matching page not found')
+    }
+  }
+)
 
 const emit = defineEmits([
   'update:adFormatRadio',

+ 434 - 132
src/views/adManage/sb/campaigns/CreateCampaigns/component/FocusCreativity.vue

@@ -17,7 +17,7 @@
         <el-form-item label="广告名称" prop="name">
           <el-input v-model="ruleForm.name" style="width: 50%" />
         </el-form-item>
-        <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px">
+        <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px" v-loading="createLoading">
           <div style="width: 50%; padding-left: 5px; border-right: 1px solid #dddfe6">
             <el-scrollbar height="700px">
               <el-collapse v-model="activeNames" @change="handleChange" style="border-top: none; border-bottom: none">
@@ -27,7 +27,15 @@
                     <el-input v-model="ruleForm.brandName" placeholder="请输入品牌名称" style="padding: 0 0 5px 0"></el-input>
                   </el-form-item>
 
-                  <el-upload v-model:file-list="fileList" action="#" accept=".png, .jpg" :limit="1" list-type="picture-card" :auto-upload="false">
+                  <el-upload
+                    v-model:file-list="fileList"
+                    :on-change="changeFile"
+                    v-loading="upLoading"
+                    action="#"
+                    accept=".png, .jpg"
+                    :limit="1"
+                    list-type="picture-card"
+                    :auto-upload="false">
                     <el-icon><Plus /></el-icon>
                     <template #file="{ file }">
                       <div>
@@ -46,7 +54,7 @@
                       <div style="margin-top: 10px">
                         <div style="display: flex; align-items: center; justify-content: space-between">
                           <span style="line-height: 17px; font-weight: 600; color: #1e2128">徽标规格</span>
-                          <el-button type="primary" :icon="Picture" @click="openDialog">从素材库中选择</el-button>
+                          <el-button type="primary" :icon="Picture" @click="openDialog" disabled="true">从素材库中选择</el-button>
                         </div>
                         <div class="introduce-item">1、图片大小: 400x400 像素或更大</div>
                         <div class="introduce-item">2、文件大小: 1MB 或更小</div>
@@ -72,33 +80,78 @@
                     <img w-full :src="dialogImageUrl" alt="Preview Image" />
                   </el-dialog>
                 </el-collapse-item>
+
                 <el-collapse-item name="commodity" v-loading="commodityLoading" style="padding-right: 10px">
                   <template #title>编辑品牌旗舰店页面</template>
-                  <div v-for="(item, index) in flattenedCommodityCard" :key="index" style="margin: 0 0 5px 0">
-                    <el-card shadow="hover" body-style="padding: 10px; display: flex;">
-                      <div style="margin-right: 8px; line-height: normal">
-                        <el-image class="img-box" :src="item.image_link" />
-                      </div>
-                      <div style="position: relative">
-                        <el-tooltip class="box-item" effect="dark" :content="item.title" placement="top">
-                          <div class="double-line">{{ item.title }}</div>
-                        </el-tooltip>
-                        <span>
-                          <span style="color: #6d7784">ASIN: </span>
-                          <span class="data-color" style="margin-right: 8px">{{ item.asin }}</span>
-                        </span>
-                        <el-button
-                          type="primary"
-                          size="small"
-                          link
-                          @click="() => openCommodityDialog(index)"
-                          style="position: absolute; bottom: 2px; right: 0">
-                          更换商品
-                        </el-button>
+                  <div v-for="(storePage, index) in topStorePages" :key="index" style="margin-bottom: 10px; width: 85%">
+                    <el-card shadow="hover" body-style="padding: 10px;">
+                      <div style="margin-right: 8px; line-height: normal; display: flex; align-items: center">
+                        <el-image class="img-box" :src="storePage.storePageLink" />
+                        <div style="margin-left: 15px">
+                          <span><span style="color: #6d7784">当前名称:</span>{{ storePage.storePageName }}</span>
+                          <div style="margin-bottom: 5px"><span style="color: #6d7784">ASIN: </span>{{ storePage.asin }}</div>
+                          <el-input v-model="storePage.inputName" style="width: 300px" placeholder="修改品牌页面名称"></el-input>
+                        </div>
+                        <div class="card-operation">
+                          <el-button link type="primary" @click="changePicture(storePage.storePageUrl, index)" style="margin-bottom: 10px"
+                            >更换图片</el-button
+                          >
+                          <el-button link type="primary" @click="changePage(storePage.storePageUrl, index)">更换页面</el-button>
+                        </div>
                       </div>
                     </el-card>
                   </div>
                 </el-collapse-item>
+                <el-dialog v-model="commodityDialog" title="更换图片" width="50%">
+                  <el-radio-group
+                    v-loading="dialogLoading3"
+                    v-model="selectedCommodity"
+                    style="display: flex; flex-direction: column; align-content: flex-start; align-items: flex-start">
+                    <div v-for="(item, index) in stock" :key="index">
+                      <el-radio :label="item.asin" style="height: 80px; border-bottom: 1px solid #ccc">
+                        <div style="padding: 10px; display: flex; align-items: center">
+                          <div style="margin-right: 8px; line-height: normal">
+                            <el-image class="img-box" :src="item.image_link" />
+                          </div>
+                          <div style="position: relative">
+                            <el-tooltip class="box-item" effect="dark" :content="item.title" placement="top">
+                              <div class="double-line">{{ item.title }}</div>
+                            </el-tooltip>
+                            <span>
+                              <span style="color: #6d7784">ASIN: </span>
+                              <span class="data-color" style="margin-right: 8px">{{ item.asin }}</span>
+                            </span>
+                          </div>
+                        </div>
+                      </el-radio>
+                    </div>
+                  </el-radio-group>
+                  <div style="margin-top: 20px; display: flex; justify-content: center">
+                    <el-button type="primary" :disabled="!selectedCommodity" @click="handleSelectedStore">确定</el-button>
+                  </div>
+                </el-dialog>
+                <el-dialog v-model="pageDialog" title="更换页面" width="50%">
+                  <el-radio-group v-loading="pageDialogLoading" v-model="selectedCommodity" @change="handlePageChange" class="radio-group-item">
+                    <div v-for="(item, index) in storePageData.storePageInfo" :key="index" style="width: 100%">
+                      <el-radio
+                        :label="item.storePageId"
+                        class="radio-item"
+                        :disabled="topStorePages.some((storePage) => storePage.storePageName === item.storePageName)">
+                        <div class="radio-item-content">
+                          <div style="position: relative">
+                            <el-tooltip class="box-item" effect="dark" :content="item.storePageName" placement="top">
+                              <div class="double-line">{{ item.storePageName }}</div>
+                            </el-tooltip>
+                          </div>
+                        </div>
+                      </el-radio>
+                    </div>
+                  </el-radio-group>
+                  <!-- <div style="margin-top: 20px; display: flex; justify-content: center">
+                    <el-button type="primary" :disabled="!selectedCommodity">确定</el-button>
+                  </div> -->
+                </el-dialog>
+
                 <el-collapse-item name="4" style="padding-right: 10px">
                   <template #title> <span style="color: #e47470; margin-right: 4px">*</span>标题</template>
                   <el-form-item prop="title">
@@ -109,7 +162,9 @@
             </el-scrollbar>
           </div>
           <div style="width: 50%; padding: 0 10px; position: relative">
-            <el-button type="primary" plain @click="clickSave" style="position: absolute; top: 92%; left: 46%">保存</el-button>
+            <el-button type="primary" plain @click="submitForm(ruleFormRef)" :disabled="!fileList.length" style="position: absolute; top: 92%; left: 46%"
+              >保存</el-button
+            >
           </div>
         </div>
       </el-form>
@@ -149,7 +204,7 @@
         </span>
       </template>
     </el-dialog>
-    <el-dialog v-model="lifeStyleDialog" title="从素材库中选择" width="65%">
+    <!-- <el-dialog v-model="lifeStyleDialog" title="从素材库中选择" width="65%">
       <el-input :prefix-icon="Search"></el-input>
       <div class="grid-container">
         <div
@@ -183,51 +238,36 @@
           <el-button type="primary" @click="centerDialogVisible = false">确定</el-button>
         </span>
       </template>
-    </el-dialog>
-    <el-dialog v-model="commodityDialog" title="更换商品" width="50%">
-      <el-radio-group
-        v-loading="dialogLoading3"
-        v-model="selectedCommodity"
-        style="display: flex; flex-direction: column; align-content: flex-start; align-items: flex-start">
-        <div v-for="(item, index) in flattenedReplaceableCommodity" :key="index">
-          <el-radio :label="item.asin" style="height: 80px; border-bottom: 1px solid #ccc">
-            <div style="padding: 10px; display: flex; align-items: center">
-              <div style="margin-right: 8px; line-height: normal">
-                <el-image class="img-box" :src="item.image_link" />
-              </div>
-              <div style="position: relative">
-                <el-tooltip class="box-item" effect="dark" :content="item.title" placement="top">
-                  <div class="double-line">{{ item.title }}</div>
-                </el-tooltip>
-                <span>
-                  <span style="color: #6d7784">ASIN: </span>
-                  <span class="data-color" style="margin-right: 8px">{{ item.asin }}</span>
-                </span>
-              </div>
-            </div>
-          </el-radio>
-        </div>
-      </el-radio-group>
-      <div style="margin-top: 20px; display: flex; justify-content: center">
-        <el-button type="primary" :disabled="!selectedCommodity" @click="handleSelectedCommodity">确定</el-button>
-      </div>
-    </el-dialog>
+    </el-dialog> -->
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, inject, Ref, watch, computed } from 'vue'
+import { reactive, ref, inject, Ref, watch, computed, onMounted } from 'vue'
 import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Picture, Search, Delete, Download, ZoomIn } from '@element-plus/icons-vue'
 import type { UploadFile } from 'element-plus'
-import { getAssets, getLifeStyleAssets, getPageAsins, getCommodityCard } from '../api/index'
+import emitter from '/@/utils/emitter'
+import {
+  getAssets,
+  getLifeStyleAssets,
+  getPageAsins,
+  getCommodityCard,
+  getStoreurl,
+  getDefaultSpotlightAsin,
+  getSellerInStock,
+  postStoreSpotlight,
+  uploadFile,
+  checkAsset,
+} from '../api/index'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
 
+const createLoading = ref(false)
 const ruleFormRef = ref<FormInstance>()
 
 interface RuleForm {
@@ -252,6 +292,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   await formEl.validate((valid, fields) => {
     if (valid) {
       console.log('submit!')
+      createStoreSpotlight()
     } else {
       console.log('error submit!', fields)
     }
@@ -260,9 +301,9 @@ const submitForm = async (formEl: FormInstance | undefined) => {
 
 const activeNames = ref(['1'])
 const handleChange = (val: string[]) => {
-  console.log(val)
+  // console.log(val)
   if (val.includes('commodity')) {
-    getCommodityCardData()
+    // getCommodityCardData()
   }
 }
 
@@ -296,15 +337,6 @@ const selectedImageUrl = ref('')
 const centerDialogVisible = ref(false)
 const cards = reactive([])
 
-function handleRemove(file: UploadFile) {
-  fileList.value = []
-}
-
-function handlePictureCardPreview(file: UploadFile) {
-  dialogImageUrl.value = file.url!
-  dialogVisible.value = true
-}
-
 function selectCard(item) {
   if (isSelected(item.id)) {
     selectedCards.value = selectedCards.value.filter((card) => card.id !== item.id)
@@ -404,82 +436,339 @@ async function getLifeStyleAssetsData() {
   }
 }
 
-function openLifeStyleDialog() {
-  lifeStyleDialog.value = true
-  getLifeStyleAssetsData()
+// function openLifeStyleDialog() {
+//   lifeStyleDialog.value = true
+//   getLifeStyleAssetsData()
+// }
+
+// 获取商品数据
+const topStorePages = ref([])
+const commodityLoading = ref(false)
+const dialogLoading3 = ref(false)
+let subpageslist = []
+
+async function getDefaultCommodityData() {
+  try {
+    commodityLoading.value = true
+    const resp = await getDefaultSpotlightAsin({ profile_id: '3006125408623189' })
+    if (resp.code === 2000) {
+      topStorePages.value = resp.data.defaultlist.map((item) => ({
+        storePageName: item.storePageName,
+        storePageLink: item.image_link,
+        storePageUrl: item.storePageUrl,
+        asin: item.asin,
+        inputName: item.storePageName, // 新增字段用于存储输入值
+      }))
+      subpageslist = topStorePages.value.map((item) => ({
+      asin: item.asin,
+      pageTitle: item.inputName,
+      url: item.storePageUrl,
+  }))
+    }
+  } catch (error) {
+    console.error('Error in getDefaultCommodityData:', error)
+  } finally {
+    commodityLoading.value = false
+  }
 }
 
-const pageOptionsValue = inject<Ref>('pageOptionsValue')
 const asinList = ref([])
-const commodityLoading = ref(false)
+const stock = ref([])
+const currentEditingIndex = ref(null)
+const selectedCommodityData = ref(null)
 
-async function getCommodityCollapseData() {
-  commodityLoading.value = true
+async function changePicture(pageUrl, index) {
+  commodityDialog.value = true
+  dialogLoading3.value = true
+
+  currentEditingIndex.value = index
   try {
     const query = {
       profile_id: profile.value.profile_id,
-      pageurl: pageOptionsValue.value,
+      pageurl: pageUrl,
     }
     const response = await getPageAsins(query)
     asinList.value = response.data.asinList
-    console.log('asinList', asinList.value)
+    getStock()
+    dialogLoading3.value = false
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
+
+async function getStock() {
+  try {
+    const obj = {
+      profile_id: profile.value.profile_id,
+      asinlist: asinList.value,
+    }
+    const response = await getSellerInStock(obj)
+    stock.value = response.data
+  } catch (error) {
+    console.error('Error in getStock:', error)
+  }
+}
+
+function handleSelectedStore() {
+  if (currentEditingIndex.value !== null) {
+    selectedCommodityData.value = stock.value.find((item) => item.asin === selectedCommodity.value)
+    if (selectedCommodityData.value) {
+      // 更新图片链接和ASIN
+      topStorePages.value[currentEditingIndex.value].storePageLink = selectedCommodityData.value.image_link
+      topStorePages.value[currentEditingIndex.value].asin = selectedCommodityData.value.asin
+      // 更新输入框的绑定值以便可以继续编辑名称
+      topStorePages.value[currentEditingIndex.value].inputName = topStorePages.value[currentEditingIndex.value].inputName
+      console.log(topStorePages.value)
+    }
+    // 重置当前编辑索引和选中的商品
+    currentEditingIndex.value = null
+    selectedCommodity.value = null
+    commodityDialog.value = false
+  }
+}
+
+// 更换页面功能
+const pageDialog = ref(false)
+const storePageData = ref({ storePageInfo: [] })
+const pageDialogLoading = ref(false)
+const selectedPageUrl = ref('')
+const firstObj: any = ref({})
+const pageAsinList = ref([])
+const selectedPageName = ref('')
+
+async function changePage(pageUrl, index) {
+  pageDialog.value = true
+  pageDialogLoading.value = true
+  currentEditingIndex.value = index
+  try {
+    const response = await getStoreurl({ profile_id: profile.value.profile_id })
+    storePageData.value = response.data
   } catch (error) {
     console.log('error:', error)
   } finally {
-    commodityLoading.value = false
+    pageDialogLoading.value = false
   }
 }
 
-function clickSave() {
-  console.log(123, fileList.value)
+async function getAsinList(pageurl) {
+  try {
+    const query = {
+      profile_id: profile.value.profile_id,
+      pageurl: pageurl,
+    }
+    const resp = await getPageAsins(query)
+    pageAsinList.value = resp.data.asinList
+
+    changeCardData()
+  } catch (error) {
+    console.log('error:', error)
+  }
 }
 
-let lastQueriedAsins = []
-const commodityCard = ref([])
-async function getCommodityCardData() {
+async function handlePageChange(selectedPageId) {
+  const selectedPage = storePageData.value.storePageInfo.find((page) => page.storePageId === selectedPageId)
+  if (selectedPage) {
+    selectedPageUrl.value = selectedPage.storePageUrl
+    selectedPageName.value = selectedPage.storePageName
+    pageDialog.value = false
+    await getAsinList(selectedPageUrl.value)
+  } else {
+    console.error('Selected page not found in storePageData')
+  }
+}
+
+async function changeCardData() {
   try {
-    commodityLoading.value = true
-    const topAsins = asinList.value.slice(0, 3)
+    const query = {
+      profile_id: profile.value.profile_id,
+      asinlist: pageAsinList.value,
+    }
+    const response = await getSellerInStock(query)
+    if (response && response.data.length > 0) {
+      // 将response的第一个元素赋值给firstObj
+      firstObj.value = response.data[0]
+
+      // 更新图片链接和ASIN
+      topStorePages.value[currentEditingIndex.value].storePageLink = firstObj.value.image_link
+      topStorePages.value[currentEditingIndex.value].asin = firstObj.value.asin
+      topStorePages.value[currentEditingIndex.value].storePageName = selectedPageName.value
+      topStorePages.value[currentEditingIndex.value].inputName = selectedPageName.value
+      currentEditingIndex.value = null
+    } else {
+      console.error('No data')
+    }
+  } catch (error) {
+    console.error('error:', error)
+  }
+}
 
-    const newAsins = topAsins.filter((asin) => !lastQueriedAsins.includes(asin))
-    if (newAsins.length === 0) {
-      commodityLoading.value = false
-      return // 如果没有新的 ASIN,直接返回
+// 创建创意
+let brandName = ''
+let brandEntityId = ''
+const respAdGroupId = inject<Ref>('respAdGroupId')
+let brandLogoCrop = {}
+
+const upLoading = ref(false)
+let respAssetId = ''
+
+function handleRemove(file: UploadFile) {
+  fileList.value = []
+}
+
+function handlePictureCardPreview(file: UploadFile) {
+  dialogImageUrl.value = file.url!
+  dialogVisible.value = true
+}
+
+function changeFile(file: UploadFile) {
+  handleUpload(file)
+}
+
+async function handleUpload(file: UploadFile) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'IMAGE')
+  formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
+  upLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    respAssetId = resp.data.assetId
+    const { width, height } = resp.data.fileMetadata
+    brandLogoCrop = {
+      width,
+      height,
+      top: 0,
+      left: 0,
     }
 
-    lastQueriedAsins = [...topAsins]
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    upLoading.value = false
+  }
+}
 
-    // 清空commodityCard,为新数据做准备
-    commodityCard.value = []
+async function createStoreSpotlight() {
+  createLoading.value = true
+  try {
+    const query = {
+      profile_id: profile.value.profile_id,
+      url: 'https://www.amazon.com/stores/page/1D1DD2FD-CF54-4FE5-B1A0-9E01F12F8144',
+      name: ruleForm.name,
+      state: 'PAUSED',
+      adGroupId: respAdGroupId.value,
+      brandName: brandName,
+      brandLogoAssetID: respAssetId,
+      brandLogoCrop: brandLogoCrop,
+      consentToTranslate: false,
+      subpageslist: subpageslist,
+      headline: ruleForm.title,
+    }
+    const response = await postStoreSpotlight(query)
+    if (response.data.creative_state == 'success') {
+      ElMessage({ message: '创建成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('error:', error)
+  } finally {
+    createLoading.value = false
+  }
+}
 
-    // 对每个新的 ASIN 发送请求
-    for (const asin of newAsins) {
-      const query = {
-        profile_id: profile.value.profile_id,
-        asin: asin,
-      }
+watch(topStorePages, () => {
+    subpageslist = topStorePages.value.map((item) => ({
+    asin: item.asin,
+    pageTitle: item.inputName,
+    url: item.storePageUrl,
+  }))
+  console.log('subpageslist', subpageslist)
+},{deep: true})
 
-      try {
-        const response = await getCommodityCard(query)
-        commodityCard.value.push(response.data)
-        // console.log('Response for ASIN', asin, ':', response)
-      } catch (error) {
-        console.log('Error for ASIN', asin, ':', error)
-      }
+onMounted(() => {
+  emitter.on('spotlight-shop', (newValue: any) => {
+    brandName = newValue.brandRegistryName
+    brandEntityId = newValue.brandEntityId
+  })
+})
+
+const focusShop = inject<Ref>('focusShop')
+
+async function getCommodityCollapseData() {
+  commodityLoading.value = true
+  try {
+    const query = {
+      profile_id: profile.value.profile_id,
+      pageurl: focusShop.value,
     }
+    const response = await getPageAsins(query)
+    asinList.value = response.data.asinList
+    console.log('asinList', asinList.value)
   } catch (error) {
-    console.log('Outer error:', error)
+    console.log('error:', error)
   } finally {
     commodityLoading.value = false
   }
 }
 
+// let lastQueriedAsins = []
+const commodityCard = ref([])
+// async function getCommodityCardData() {
+//   try {
+//     commodityLoading.value = true
+//     const topAsins = asinList.value.slice(0, 3)
+
+//     const newAsins = topAsins.filter((asin) => !lastQueriedAsins.includes(asin))
+//     if (newAsins.length === 0) {
+//       commodityLoading.value = false
+//       return // 如果没有新的 ASIN,直接返回
+//     }
+
+//     lastQueriedAsins = [...topAsins]
+
+//     // 清空commodityCard,为新数据做准备
+//     commodityCard.value = []
+
+//     // 对每个新的 ASIN 发送请求
+//     for (const asin of newAsins) {
+//       const query = {
+//         profile_id: profile.value.profile_id,
+//         asin: asin,
+//       }
+
+//       try {
+//         const response = await getCommodityCard(query)
+//         commodityCard.value.push(response.data)
+//         // console.log('Response for ASIN', asin, ':', response)
+//       } catch (error) {
+//         console.log('Error for ASIN', asin, ':', error)
+//       }
+//     }
+//   } catch (error) {
+//     console.log('Outer error:', error)
+//   } finally {
+//     commodityLoading.value = false
+//   }
+// }
+
 // 更改商品功能
 const commodityDialog = ref(false)
 const selectedCommodity = ref()
 const replaceableCommodity = ref([])
-const dialogLoading3 = ref(false)
-let currentEditingIndex = ref(null)
 
 function openCommodityDialog(index) {
   currentEditingIndex.value = index
@@ -518,28 +807,6 @@ async function getAdditionalCommodityData() {
   }
 }
 
-function handleSelectedCommodity() {
-  if (currentEditingIndex.value !== null && selectedCommodity.value) {
-    const selectedCommodityData = flattenedReplaceableCommodity.value.find((item) => item.asin === selectedCommodity.value)
-    if (selectedCommodityData) {
-      commodityCard.value[currentEditingIndex.value] = selectedCommodityData
-    }
-    commodityDialog.value = false
-    console.log('commodityCard', commodityCard.value)
-    currentEditingIndex.value = null
-    selectedCommodity.value = null
-  }
-}
-
-watch(
-  () => pageOptionsValue.value,
-  async () => {
-    await getCommodityCollapseData()
-    getCommodityCardData()
-    getAdditionalCommodityData()
-  }
-)
-
 const flattenedCommodityCard = computed(() => {
   return commodityCard.value.flat()
 })
@@ -547,6 +814,19 @@ const flattenedCommodityCard = computed(() => {
 const flattenedReplaceableCommodity = computed(() => {
   return replaceableCommodity.value.flat()
 })
+// watch(focusShop.value,
+//   async () => {
+//     await getCommodityCollapseData()
+//     getCommodityCardData()
+//     getAdditionalCommodityData()
+//   }
+// )
+onMounted(async () => {
+  await getDefaultCommodityData()
+  // await getCommodityCollapseData()
+  // getCommodityCardData()
+  // getAdditionalCommodityData()
+})
 </script>
 
 <style scoped>
@@ -644,7 +924,6 @@ const flattenedReplaceableCommodity = computed(() => {
   display: block;
   margin-bottom: 10px; /* 或根据需要调整 */
 }
-
 .upload-content {
   text-align: center;
   padding: 20px;
@@ -660,7 +939,7 @@ const flattenedReplaceableCommodity = computed(() => {
   background-color: #3569d6;
 }
 .img-box {
-  width: 60px;
+  min-width: 60px;
   height: 60px;
   border: 1px solid rgb(194, 199, 207);
   border-radius: 4px;
@@ -675,4 +954,27 @@ const flattenedReplaceableCommodity = computed(() => {
   white-space: pre-wrap;
   word-break: break-word;
 }
+.card-operation {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  justify-content: center;
+  margin-left: 30px;
+}
+.radio-group-item {
+  display: flex;
+  flex-direction: column;
+  align-content: flex-start;
+  align-items: flex-start;
+}
+.radio-item {
+  height: 80px;
+  width: 100%;
+  border-bottom: 1px solid #ccc;
+}
+.radio-item-content {
+  padding: 10px;
+  display: flex;
+  align-items: center;
+}
 </style>

+ 7 - 0
src/views/adManage/sb/campaigns/CreateCampaigns/component/ProductSetCommodity.vue

@@ -158,6 +158,7 @@ import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
 import type { Ref } from 'vue'
 import { inject, onMounted, ref, watch } from 'vue'
+import emitter from '/@/utils/emitter'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { request } from '/@/utils/service'
 
@@ -365,6 +366,12 @@ function handleSizeChange(newSize) {
   currentPage.value = 1 // 重置到第一页
 }
 
+watch(addedTableData, () => {
+  if (addedTableData.value.length > 0) {
+    emitter.emit('addedTableData', addedTableData.value)
+  }
+},{deep:true})
+
 const headerCellStyle = (args) => {
   if (args.rowIndex === 0) {
     return {

+ 182 - 22
src/views/adManage/sb/campaigns/CreateCampaigns/component/ProductSetCreativity2.vue

@@ -27,10 +27,14 @@
                     <el-input v-model="ruleForm.brandName" placeholder="请输入品牌名称" style="padding: 0 10px 5px 0"></el-input>
                   </el-form-item>
                   <el-upload
-                    class="upload-demo"
                     drag
-                    action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
-                    multiple
+                    v-model:file-list="fileList"
+                    :on-change="changeFile"
+                    v-loading="upLoading"
+                    action="#"
+                    accept=".png, .jpg"
+                    :auto-upload="false"
+                    :on-remove="handleRemove"
                     style="padding-right: 10px">
                     <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                     <div class="el-upload__text">Drop file here or <em>click to upload</em></div>
@@ -64,11 +68,14 @@
                   <template #title>自定义图片(可选)</template>
                   <el-upload
                     class="avatar-uploader"
-                    style="padding-right: 10px"
-                    action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
-                    :show-file-list="false"
-                    :on-success="handleAvatarSuccess"
-                    :before-upload="beforeAvatarUpload">
+                    v-model:file-list="fileList"
+                    :on-change="changeCustomFile"
+                    v-loading="upCustomLoading"
+                    action="#"
+                    accept=".png, .jpg"
+                    :auto-upload="false"
+                    :on-remove="handleRemove"
+                    style="padding-right: 10px">
                     <img v-if="imageUrl" :src="imageUrl" class="avatar" />
                     <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
                     <template #tip>
@@ -86,9 +93,26 @@
                   </el-upload>
                 </el-collapse-item>
 
-                <el-collapse-item name="3">
+                <el-collapse-item name="3" style="padding: 0 10px 0 5px">
                   <template #title> <span style="color: #e47470; margin-right: 4px">*</span>商品</template>
-                  拖动带编号的选项卡,可以重新排列商品。商品可更换成你落地页上的任何商品。
+                  <div v-for="item in commodityCard" :key="item.asin">
+                    <el-card shadow="hover" body-style="padding: 10px">
+                      <div style="padding: 10px; display: flex; align-items: center">
+                        <div style="margin-right: 8px; line-height: normal">
+                          <el-image class="img-box" :src="item.image_link" />
+                        </div>
+                        <div style="position: relative">
+                          <el-tooltip class="box-item" effect="dark" :content="item.title" placement="top">
+                            <div class="single-line">{{ item.title }}</div>
+                          </el-tooltip>
+                          <span>
+                            <span style="color: #6d7784">ASIN: </span>
+                            <span class="data-color" style="margin-right: 8px">{{ item.asin }}</span>
+                          </span>
+                        </div>
+                      </div>
+                    </el-card>
+                  </div>
                 </el-collapse-item>
                 <el-collapse-item name="4">
                   <template #title> <span style="color: #e47470; margin-right: 4px">*</span>标题</template>
@@ -106,10 +130,19 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
+import { reactive, ref, inject, Ref, watch, onMounted } from 'vue'
 import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
+import { checkAsset, uploadFile } from '../api/index'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Picture } from '@element-plus/icons-vue'
+import emitter from '/@/utils/emitter'
+import { storeToRefs } from 'pinia'
+import { useShopInfo } from '/@/stores/shopInfo'
+
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+const addedTableDataForVc2 = inject<Ref>('addedTableDataForVc2')
+
 
 interface RuleForm {
   name: string
@@ -146,19 +179,10 @@ const handleChange = (val: string[]) => {
   console.log(val)
 }
 
-const fileList = ref<UploadUserFile[]>([
-  {
-    name: 'element-plus-logo.svg',
-    url: 'https://element-plus.org/images/element-plus-logo.svg',
-  },
-  {
-    name: 'element-plus-logo2.svg',
-    url: 'https://element-plus.org/images/element-plus-logo.svg',
-  },
-])
+const fileList = ref<UploadUserFile[]>([])
 
 const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
-  console.log(file, uploadFiles)
+  fileList.value = []
 }
 
 const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
@@ -176,6 +200,123 @@ const beforeRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => {
   )
 }
 
+function changeFile(file) {
+  handleUpload(file)
+}
+
+const upLoading = ref(false)
+let respAssetId = ''
+let brandEntityId = ''
+let brandLogoCrop = {}
+
+async function handleUpload(file) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  // formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'IMAGE')
+  formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
+  upLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    respAssetId = resp.data.assetId
+    const { width, height } = resp.data.fileMetadata
+    brandLogoCrop = {
+      width,
+      height,
+      top: 0,
+      left: 0,
+    }
+
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    upLoading.value = false
+  }
+}
+
+// 总定义图片上传
+const upCustomLoading = ref(false)
+
+function changeCustomFile(file) {
+  handleCustomUpload(file)
+}
+
+async function handleCustomUpload(file) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  // formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'IMAGE')
+  formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
+  upCustomLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    respAssetId = resp.data.assetId
+    const { width, height } = resp.data.fileMetadata
+    brandLogoCrop = {
+      width,
+      height,
+      top: 0,
+      left: 0,
+    }
+
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    upCustomLoading.value = false
+  }
+}
+
+let asins = ref([])
+
+watch(
+  addedTableDataForVc2,
+  (newValue) => {
+    asins.value = []
+    if (Array.isArray(newValue) && newValue.length > 0) {
+      newValue.forEach((item) => {
+        if (item.asin && !asins.value.includes(item.asin)) {
+          asins.value.push(item.asin)
+        }
+      })
+    }
+    console.log('Updated ASINs:', asins.value)
+  },
+  { deep: true }
+)
+
+const commodityCard = ref([])
+onMounted(()=> {
+  emitter.on('addedTableData', (data: any) => {
+    console.log('data', data)
+    commodityCard.value = data
+    console.log(commodityCard.value)
+  })
+})
+
 function handleSelect() {
   // console.log()
 }
@@ -237,4 +378,23 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
 ::v-deep(.avatar-uploader .el-upload.el-upload--text) {
   width: 100%;
 }
+.img-box {
+  width: 60px;
+  height: 60px;
+  margin-top: 5px;
+  border: 1px solid rgb(194, 199, 207);
+  border-radius: 4px;
+}
+.single-line {
+  color: rgb(30, 33, 41);
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 1;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+.data-color {
+  color: rgb(30, 33, 41);
+}
 </style>

+ 160 - 28
src/views/adManage/sb/campaigns/CreateCampaigns/component/VideoCreativity1.vue

@@ -27,7 +27,15 @@
                     <el-input v-model="ruleForm.brandName" placeholder="请输入品牌名称" style="padding: 0 0 5px 0"></el-input>
                   </el-form-item>
 
-                  <el-upload action="#" list-type="picture-card" :auto-upload="false" v-model:file-list="fileList" :limit="1">
+                  <el-upload
+                    v-model:file-list="fileList"
+                    list-type="picture-card"
+                    :on-change="changeFile"
+                    v-loading="upLoading"
+                    action="#"
+                    accept=".png, .jpg"
+                    :limit="1"
+                    :auto-upload="false">
                     <el-icon><Plus /></el-icon>
                     <template #file="{ file }">
                       <div>
@@ -46,7 +54,7 @@
                       <div style="margin-top: 10px">
                         <div style="display: flex; align-items: center; justify-content: space-between">
                           <span style="line-height: 17px; font-weight: 600; color: #1e2128">徽标规格</span>
-                          <el-button type="primary" :icon="Picture" @click="openDialog">从素材库中选择</el-button>
+                          <el-button type="primary" :icon="Picture" disabled="true" @click="openDialog">从素材库中选择</el-button>
                         </div>
                         <div class="introduce-item">1、图片大小: 400x400 像素或更大</div>
                         <div class="introduce-item">2、文件大小: 1MB 或更小</div>
@@ -83,27 +91,25 @@
                     秒极具吸引力,并且不依靠声音来传递信息。如果您在视频中使用了文字,请确保文字清晰易辨。字幕或音频必须与将展示您广告的区域相匹配。
                   </div>
                   <el-upload
-                    v-model:file-list="fileList"
-                    class="upload-demo"
-                    action="#"
+                    v-model:file-list="videoList"
+                    :on-change="changeVideo"
+                    v-loading="videoLoading"
                     accept=".mp4, .mov"
-                    multiple
-                    :on-preview="handlePreview"
-                    :on-remove="handleRemove"
-                    :before-remove="beforeRemove"
+                    action="#"
                     :limit="1"
-                    :on-exceed="handleExceed">
-                    <el-button type="primary">上传文件</el-button>
+                    :auto-upload="false"
+                    :on-remove="handleRemoveVideo"
+                    class="upload-demo">
+                    <el-button type="primary">上传视频</el-button>
                     <template #tip>
-                      <el-button type="primary" style="margin-left: 20px">从素材库中选择</el-button>
+                      <el-button type="primary" disabled="true" style="margin-left: 20px">从素材库中选择</el-button>
                       <!-- <div class="el-upload__tip">div> -->
                       <hr style="margin: 10px 0" />
                       <div>
-                        <div style="display: flex; justify-content: space-between;">
-                          <span style="font-weight: 700; color: #1d2129; line-height: 18px; font-size: 14px;">视频文件要求:</span>
+                        <div style="display: flex; justify-content: space-between">
+                          <span style="font-weight: 700; color: #1d2129; line-height: 18px; font-size: 14px">视频文件要求:</span>
                           <el-button type="primary" link>了解更多</el-button>
                         </div>
-                        
                         <div class="tip-list-title">视频格式</div>
                         <div class="tip-item">1.纵横比:16:9</div>
                         <div class="tip-item">2.尺寸:1280 x 720 像素、1920 x 1080 像素或 3840 x 2160 像素</div>
@@ -169,7 +175,9 @@
             </el-scrollbar>
           </div>
           <div style="width: 50%; padding: 0 10px; position: relative">
-            <el-button type="primary" plain @click="clickSave" style="position: absolute; top: 92%; left: 46%">保存</el-button>
+            <el-button type="primary" plain @click="submitForm(ruleFormRef)" :disabled="!fileList.length" style="position: absolute; top: 92%; left: 46%"
+              >保存</el-button
+            >
           </div>
         </div>
       </el-form>
@@ -276,12 +284,13 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, inject, Ref, watch, computed } from 'vue'
+import { reactive, ref, inject, Ref, watch, computed, onMounted } from 'vue'
 import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Picture, Search, Delete, Download, ZoomIn } from '@element-plus/icons-vue'
 import type { UploadFile } from 'element-plus'
-import { getAssets, getLifeStyleAssets, getPageAsins, getCommodityCard } from '../api/index'
+import { getAssets, getLifeStyleAssets, getPageAsins, getCommodityCard, uploadFile, checkAsset, postBrandVideo } from '../api/index'
+import emitter from '/@/utils/emitter'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
 
@@ -312,6 +321,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
   await formEl.validate((valid, fields) => {
     if (valid) {
       console.log('submit!')
+      createBrandVideo()
     } else {
       console.log('error submit!', fields)
     }
@@ -320,7 +330,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
 
 const activeNames = ref(['1'])
 const handleChange = (val: string[]) => {
-  console.log(val)
+  // console.log(val)
   if (val.includes('commodity')) {
     getCommodityCardData()
   }
@@ -364,12 +374,14 @@ const dialogImageUrl = ref('')
 const dialogVisible = ref(false)
 const disabled = ref(false)
 const fileList = ref([])
+const videoList = ref([])
 const selectedId = ref(null)
 const hoverId = ref(null)
 const selectedCards = ref([])
 const selectedImageUrl = ref('')
 const centerDialogVisible = ref(false)
 const cards = reactive([])
+const upLoading = ref(false)
 
 function handleRemove(file: UploadFile) {
   fileList.value = []
@@ -380,6 +392,127 @@ function handlePictureCardPreview(file: UploadFile) {
   dialogVisible.value = true
 }
 
+function changeFile(file: UploadFile) {
+  handleUpload(file)
+}
+let brandLogoCrop = {}
+let respAssetId = ''
+let brandEntityId = ''
+
+async function handleUpload(file: UploadFile) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'IMAGE')
+  formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
+  upLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    respAssetId = resp.data.assetId
+    const { width, height } = resp.data.fileMetadata
+    brandLogoCrop = {
+      width,
+      height,
+      top: 0,
+      left: 0,
+    }
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    upLoading.value = false
+  }
+}
+
+const videoLoading = ref(false)
+let videoAssetIds = ''
+function handleRemoveVideo(file: UploadFile) {
+  videoList.value = []
+}
+
+function changeVideo(file: UploadFile) {
+  uploadVideo(file)
+}
+
+// 上传视频
+async function uploadVideo(file: UploadFile) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'VIDEO')
+  formData.append('assetSubTypeList', JSON.stringify(['SPONSORED_BRANDS_VIDEO']))
+  videoLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    videoAssetIds = resp.data.assetId
+    
+
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    videoLoading.value = false
+  }
+}
+
+const respAdGroupId = inject<Ref>('respAdGroupId')
+let brandName = ''
+let selectedPage = ''
+async function createBrandVideo() {
+  try {
+    const obj = {
+      profile_id: profile.value.profile_id,
+      casins: asinListFromCommodityCard.value,
+      url: selectedPage,
+      name: ruleForm.name,
+      state: 'PAUSED',
+      adGroupId: respAdGroupId.value,
+      brandName: brandName,
+      brandLogoAssetID: respAssetId,
+      brandLogoCrop: brandLogoCrop,
+      consentToTranslate: false,
+      videoAssetIds: videoAssetIds,
+      headline: ruleForm.title,
+    }
+    const response = await postBrandVideo(obj)
+    console.log('response', response)
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
+
+onMounted(() => {
+  emitter.on('video-shop', (value: any) => {
+    brandName = value.brandRegistryName
+    brandEntityId = value.brandEntityId
+  })
+  emitter.on('page', (newPageValue: any) => {
+    selectedPage = newPageValue
+  })
+})
+
 function selectCard(item) {
   if (isSelected(item.id)) {
     selectedCards.value = selectedCards.value.filter((card) => card.id !== item.id)
@@ -521,12 +654,9 @@ async function getCommodityCardData() {
       commodityLoading.value = false
       return // 如果没有新的 ASIN,直接返回
     }
-
     lastQueriedAsins = [...topAsins]
-
     // 清空commodityCard,为新数据做准备
     commodityCard.value = []
-
     // 对每个新的 ASIN 发送请求
     for (const asin of newAsins) {
       const query = {
@@ -566,22 +696,17 @@ async function getAdditionalCommodityData() {
     dialogLoading3.value = true
     // 获取除前三个之外的所有 ASIN
     const additionalAsins = asinList.value.slice(3)
-
     // 清空 replaceableCommodity,为新数据做准备
     replaceableCommodity.value = []
-
     // 对每个额外的 ASIN 发送请求
     for (const asin of additionalAsins) {
       const query = {
         profile_id: profile.value.profile_id,
         asin: asin,
       }
-
       try {
         const response = await getCommodityCard(query)
         replaceableCommodity.value.push(response.data)
-        console.log('🚀 ~ getAdditionalCommodityData ~ replaceableCommodity-->>', replaceableCommodity.value)
-        // console.log('Response for additional ASIN', asin, ':', response)
       } catch (error) {
         console.log('Error for additional ASIN', asin, ':', error)
       }
@@ -600,7 +725,6 @@ function handleSelectedCommodity() {
       commodityCard.value[currentEditingIndex.value] = selectedCommodityData
     }
     commodityDialog.value = false
-    console.log('commodityCard', commodityCard.value)
     currentEditingIndex.value = null
     selectedCommodity.value = null
   }
@@ -619,6 +743,14 @@ const flattenedCommodityCard = computed(() => {
   return commodityCard.value.flat()
 })
 
+const asinListFromCommodityCard = computed(() => {
+  return flattenedCommodityCard.value.map((item) => item.asin)
+})
+
+// watch(asinListFromCommodityCard, (newAsinList) => {
+//   console.log('New ASIN list:', newAsinList);
+// });
+
 const flattenedReplaceableCommodity = computed(() => {
   return replaceableCommodity.value.flat()
 })

+ 116 - 16
src/views/adManage/sb/campaigns/CreateCampaigns/component/VideoCreativity2.vue

@@ -17,7 +17,7 @@
         <el-form-item label="广告名称" prop="name">
           <el-input v-model="ruleForm.name" style="width: 50%" />
         </el-form-item>
-        <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px">
+        <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px" v-loading="createLoading">
           <div style="width: 50%; padding-left: 5px; border-right: 1px solid #dddfe6">
             <el-scrollbar height="700px">
               <el-collapse v-model="activeNames" @change="handleChange" style="border-top: none; border-bottom: none">
@@ -35,21 +35,21 @@
                     <div class="upload-button-group">
                       <el-upload
                         v-model:file-list="fileList"
-                        class="upload-demo"
-                        action="#"
+                        :on-change="changeFile"
+                        v-loading="upLoading"
                         accept=".mp4, .mov"
-                        :on-preview="handlePreview"
+                        action="#"
+                        :limit="1"
+                        :auto-upload="false"
                         :on-remove="handleRemove"
-                        :before-remove="beforeRemove"
-                        :limit="3"
-                        :on-exceed="handleExceed">
+                        class="upload-demo">
                         <el-button type="primary">上传文件</el-button>
 
-                        <template #tip>
-                          <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
-                        </template>
+                        <!-- <template #tip>
+                          <div class="el-upload__tip"><el-button type="primary" :icon="Picture"  :disabled="true" @click="handleSelect">从素材库中选择</el-button></div>
+                        </template> -->
                       </el-upload>
-                      <el-button type="primary" :icon="Picture" style="margin-left: -120px" @click="handleSelect">从素材库中选择</el-button>
+                      <!-- <el-button type="primary" :icon="Picture" style="margin-left: -120px" :disabled="true" @click="handleSelect">从素材库中选择</el-button> -->
                     </div>
                   </div>
                 </el-collapse-item>
@@ -105,7 +105,12 @@
             </el-scrollbar>
           </div>
           <div style="width: 50%; padding: 0 10px; position: relative">
-            <el-button type="primary" plain @click="clickSave" :disabled="!commodityCard.length" style="position: absolute; top: 92%; left: 46%"
+            <el-button
+              type="primary"
+              plain
+              @click="submitForm(ruleFormRef)"
+              :disabled="!commodityCard.length && addedTableDataForVc2"
+              style="position: absolute; top: 92%; left: 46%"
               >保存</el-button
             >
           </div>
@@ -151,11 +156,12 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, watch, inject, Ref } from 'vue'
+import { reactive, ref, watch, inject, Ref, onMounted } from 'vue'
 import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Picture, Search } from '@element-plus/icons-vue'
-import { getVideoAssets, videoDetailCreate } from '../api/index'
+import { getVideoAssets, videoDetailCreate, uploadFile, checkAsset, postVideo } from '../api/index'
+import emitter from '/@/utils/emitter'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
 
@@ -175,13 +181,24 @@ const ruleForm = reactive<RuleForm>({
 const rules = reactive<FormRules<RuleForm>>({
   name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
 })
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      createVideo()
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
 
 const activeNames = ref(['video'])
 const handleChange = (val: string[]) => {
   // console.log(val)
 }
 
-const fileList = ref<UploadUserFile[]>([])  // 上传的文件列表
+const fileList = ref<UploadUserFile[]>([]) // 上传的文件列表
 
 const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
   fileList.value.length = 0
@@ -203,6 +220,89 @@ const beforeRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => {
   )
 }
 
+function changeFile(file) {
+  // console.log('file', file)
+  handleUpload(file)
+}
+
+const upLoading = ref(false)
+let brandLogoCrop = {}
+let respAssetId = ''
+let brandEntityId = ''
+
+async function handleUpload(file) {
+  const formData = new FormData()
+  formData.append('file', file.raw)
+  formData.append('profile_id', profile.value.profile_id)
+  formData.append('brandEntityId', brandEntityId)
+  formData.append('assetType', 'VIDEO')
+  formData.append('assetSubTypeList', JSON.stringify([]))
+  upLoading.value = true
+  try {
+    const response = await uploadFile(formData)
+    const fileName = response.data.file_name
+    const obj = {
+      profile_id: profile.value.profile_id,
+      file_name: fileName,
+    }
+    const resp = await checkAsset(obj)
+    respAssetId = resp.data.assetId
+    console.log('🚀 ~ handleUpload ~ respAssetId-->>', respAssetId)
+    if (resp.data.checkresult == 'success') {
+      ElMessage({ message: '上传成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('上传失败:', error)
+  } finally {
+    upLoading.value = false
+  }
+}
+
+const createLoading = ref(false)
+async function createVideo() {
+  createLoading.value = true
+  try {
+    const obj = {
+      profile_id: profile.value.profile_id,
+      casins: asins.value,
+      name: ruleForm.name,
+      state: 'PAUSED',
+      adGroupId: respAdGroupId.value,
+      videoAssetIds: respAssetId,
+      consentToTranslate: false,
+    }
+    const response = await postVideo(obj)
+    if (response.data.creative_state == 'success') {
+      ElMessage({ message: '创建成功', type: 'success' })
+    } else {
+      ElMessage.error('上传失败')
+    }
+  } catch (error) {
+    console.error('error:', error)
+  } finally {
+    createLoading.value = false
+  }
+}
+
+let asins = ref([])
+watch(
+  addedTableDataForVc2,
+  (newValue) => {
+    asins.value = []
+    if (Array.isArray(newValue) && newValue.length > 0) {
+      newValue.forEach((item) => {
+        if (item.asin && !asins.value.includes(item.asin)) {
+          asins.value.push(item.asin)
+        }
+      })
+    }
+    console.log('Updated ASINs:', asins.value)
+  },
+  { deep: true }
+)
+
 // dialog以及选择card相关功能
 const selectedId = ref(null)
 const hoverId = ref(null)
@@ -225,7 +325,7 @@ async function getAssetsData() {
   const query = {
     profile_id: profile.value.profile_id,
     assetType: 'VIDEO',
-    specCheckApprovedPrograms: 'SPONSORED_BRANDS_VIDEO'
+    specCheckApprovedPrograms: 'SPONSORED_BRANDS_VIDEO',
   }
   const response = await getVideoAssets(query)
 

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

@@ -18,6 +18,7 @@
     <VideoCreativity2 v-if="adFormatRadioValue === 'video' && arrivalsRadioValue === 'productDetailsPage'"></VideoCreativity2>
     <ProductSetCreativity2 v-if="adFormatRadioValue === 'productSet' && arrivalsRadioValue === 'newArrivals'"></ProductSetCreativity2>
     <DeliveryType></DeliveryType>
+    <p>12323{{ pageOptionsValue }}</p>
   </div>
 </template>
 
@@ -43,6 +44,7 @@ const flagshipStoreShopValue = ref('')
 const pageOptionsValue = ref('')
 const addedTableDataForVc2 = ref('')
 const focusShop = ref('')
+const focusShopLabel = ref('')
 
 provide('respCampaignId', respCampaignId)
 provide('respCampaignName', respCampaignName)
@@ -50,6 +52,7 @@ provide('respAdGroupId', respAdGroupId)
 provide('pageOptionsValue', pageOptionsValue)
 provide('addedTableDataForVc2', addedTableDataForVc2)
 provide('focusShop', focusShop)
+provide('focusShopLabel', focusShopLabel)
 
 const handleCampaignUpdate = (data) => {
   respCampaignId.value = data.id
@@ -77,6 +80,7 @@ function handleUpdateAddedData(data) {
 
 function handleFocusShopSelectChange(newValue) {
   focusShop.value = newValue
+  focusShopLabel.value = 'ZOSI'
 }
 </script>
 

+ 269 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/CommodityOperate.vue

@@ -0,0 +1,269 @@
+<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>
+          </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'
+
+
+const emit = defineEmits(['add-to-table']);
+
+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 : ''
+  })
+
+  console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
+}
+
+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) {
+  console.log('🚀 ~ refine ~ data-->>', data)
+  commodityCount.value = []
+  dialogTitle.value = data.cna
+  categoryId.value = data.cid
+  refineItem.value.push(data)
+  visible.value = true
+  dialogSelectLoading.value = true
+  setDialogOption()
+}
+
+// 定向按钮功能
+function orientate(node, data) {
+  console.log('🚀 ~ orientate ~ data-->>', data)
+  const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
+  emit('add-to-table', data);
+}
+
+function dialogFormSubmit() {}
+
+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>

+ 239 - 19
src/views/adManage/sd/campaigns/CreateCampaigns/component/CustomTarget.vue

@@ -7,7 +7,7 @@
       </div>
       <div class="main-container">
         <div class="left-container">
-          <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+          <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">
@@ -17,48 +17,240 @@
                 <template #prepend>$</template>
               </el-input>
             </div>
-            <el-tab-pane label="亚马逊受众" name="audience">亚马逊受众</el-tab-pane>
-            <el-tab-pane label="浏览再营销" name="browse">浏览再营销</el-tab-pane>
-            <el-tab-pane label="购买再营销" name="buy">购买再营销</el-tab-pane>
+            <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>
+            </el-tab-pane>
+            <el-tab-pane label="浏览再营销" name="views">
+              <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">
+                </el-tab-pane>
+                <el-tab-pane label="搜索" name="search">
+                  <CommodityOperate @add-to-table="handleAddToTable"></CommodityOperate>
+                </el-tab-pane>
+              </el-tabs>
+            </el-tab-pane>
+            <el-tab-pane label="购买再营销" name="purchases">
+              <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"> </el-tab-pane>
+                <el-tab-pane label="搜索" name="search"> </el-tab-pane>
+              </el-tabs>
+            </el-tab-pane>
           </el-tabs>
         </div>
-        <div class="right-container"></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="649" style="width: 100%">
+            <el-table-column prop="date" label="商品">
+              <template #default="{ row }">
+                <div>浏览再营销</div>
+                <div>
+                  分类: <span style="color: black">{{ row.cna ? row.cna : '--' }}</span>
+                </div>
+                <div>
+                  回溯期: <span style="color: black">{{ row.lookBack ? row.lookBack : '--' }}</span>
+                </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>
       </div>
     </el-card>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch } from 'vue'
-import type { TabsPaneContext } from 'element-plus'
+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 { useShopInfo } from '/@/stores/shopInfo'
+import { storeToRefs } from 'pinia'
 
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
 
-const activeName = ref('audience')
+// tab栏
+const topTabName = ref('audience')
 
-const handleClick = (tab: TabsPaneContext, event: Event) => {
-  console.log(tab, event)
+function handleClick(tab: TabsPaneContext, event: Event) {
+  // console.log(tab, event)
 }
 
-const bidType = ref('1')
+// tab栏顶部固定部分功能
+const bidType = ref('2')
 const bidTypeOptions = [
   {
     value: '1',
-    label: '建议竞价'
+    label: '建议竞价',
   },
   {
     value: '2',
-    label: '自定义竞价'
+    label: '自定义竞价',
   },
 ]
 const bid = ref('0.75')
 
-watch(bidType, ()=>{
-  if(bidType.value == '1'){
-    bid.value = ''
-  }else{
-    bid.value = '0.75'
+watch(
+  bidType,
+  () => {
+    if (bidType.value == '1') {
+      bid.value = ''
+    } else {
+      bid.value = '0.75'
+    }
+  },
+  { immediate: true }
+)
+
+// 亚马逊受众tab的 下拉框和输入框
+const audienceType = ref('1')
+const audienceTypeOptions = [
+  {
+    value: '1',
+    label: '所有受众',
+  },
+  {
+    value: '2',
+    label: '生活方式',
+  },
+  {
+    value: '3',
+    label: '兴趣',
+  },
+  {
+    value: '4',
+    label: '生活事件',
+  },
+  {
+    value: '5',
+    label: '场内客群',
+  },
+]
+
+const 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)
   }
-})
+}
+
+// 删除所有Table数据
+function handleDeleteAll() {
+  addedTableData.value = []
+}
+// 操作列按钮功能
+function handleButtonClick(row) {
+  addedTableData.value = addedTableData.value.filter(item => item.cid !== row.cid)
+}
+
+function changeTableHeader(row) {
+  if (row) {
+    return {
+      background: '#f5f7fa',
+    }
+  }
+}
 </script>
 
 <style scoped>
@@ -88,6 +280,17 @@ watch(bidType, ()=>{
   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;
 }
@@ -118,4 +321,21 @@ watch(bidType, ()=>{
   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;
+}
 </style>

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

@@ -1873,8 +1873,6 @@ function orientate(node, data) {
   }
 }
 
-
-
 let productTargetBidList = ref([])
 async function productTagetSave() {
   console.log('tableData', productOrientationTableData.value)