Ver código fonte

✨ feat: SP新建广告活动大部分完成

WanGxC 1 ano atrás
pai
commit
4a1d1fccef
1 arquivos alterados com 294 adições e 46 exclusões
  1. 294 46
      src/views/adManage/sp/campaigns/CreateCampaigns/index.vue

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

@@ -394,6 +394,7 @@
             </el-radio-group>
           </div>
           <!-- 关键词定向 -->
+          <!-- TODO: 未完成 不知道有什么用 -->
           <div
             style="font-size: 20px; font-weight: bold; margin-top: 30px"
             v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'">
@@ -440,8 +441,8 @@
                   </el-tab-pane>
                 </el-tabs>
               </div>
-              <div style="width: 50% ">
-                <el-card class="box-card" shadow="never" style="border: none;">
+              <div style="width: 50%">
+                <el-card class="box-card" shadow="never" style="border: none">
                   <template #header>
                     <div class="card-header">
                       <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
@@ -451,7 +452,7 @@
                   <div class="card-body" body-style="padding-bottom: -20px;">
                     <el-table
                       :data="addedKeyWordsTableData"
-                      style="width: 100%; height: 450px;"
+                      style="width: 100%; height: 450px"
                       :header-row-style="changeKeyWordsTableHeader"
                       :header-cell-style="headerCellStyle">
                       <el-table-column prop="keyword" label="关键词" width="auto" />
@@ -461,7 +462,7 @@
                     </el-table>
                   </div>
                 </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>
                 </div>
               </div>
@@ -522,14 +523,15 @@
               </div>
             </div>
           </el-form-item>
+
           <!-- 商品定向 -->
           <div style="font-size: 20px; font-weight: bold; margin-top: 30px" v-if="ruleForm.targetType == 'Goods'">商品定向</div>
           <hr v-if="ruleForm.targetType == 'Goods'" />
           <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'Goods'">
             <div
-              style="width: 100%; height: 520px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px"
+              style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px"
               v-loading="productOrientationLoading">
-              <div style="width: 50%; border-right: 1px solid #c2c7cf">
+              <div style="width: 50%; border-right: 1px solid #e5e7eb">
                 <el-tabs
                   type="border-card"
                   stretch
@@ -537,17 +539,21 @@
                   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">
                     <div style="display: flex; align-items: center">
-                      <span style="width: 40px">竞价:</span>
-                      <el-select v-model="categoryBiddingType" 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="categoryBidInput" placeholder="Please input" 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-tab-pane label="建议" name="first">
-                        <el-table :data="proposalTableData" style="width: 100%" height="342">
+                        <el-table :data="proposalTableData" style="width: 100%" height="422">
                           <el-table-column prop="proposal" label="建议" width="520">
                             <template #header> 0建议 </template>
                           </el-table-column>
@@ -563,7 +569,7 @@
                       </el-tab-pane>
                       <el-tab-pane label="搜索" name="second">
                         <el-input placeholder="请输入关键词过滤" />
-                        <el-scrollbar height="309px">
+                        <el-scrollbar height="390px">
                           <el-tree :data="searchClassifyTableData" :props="defaultProps">
                             <template #default="{ node, data }">
                               <span class="custom-tree-node">
@@ -586,7 +592,12 @@
                           <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" placeholder="请选择" v-loading="dialogSelectLoading">
+                              <el-select
+                                v-model="dialogForm.dialogselectValue"
+                                @change="dialogSelectChange"
+                                multiple
+                                placeholder="请选择"
+                                v-loading="dialogSelectLoading">
                                 <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
                               </el-select>
                             </el-form-item>
@@ -603,7 +614,7 @@
                             <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="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>
@@ -634,7 +645,6 @@
                       <el-input
                         v-model="singleGoodsBidInput"
                         :disabled="singleGoodsBidSelect == 'defaultBid'"
-                        placeholder="Please input"
                         style="width: 200px">
                         <template #prepend>$</template>
                       </el-input>
@@ -653,11 +663,27 @@
                         </el-table>
                       </el-tab-pane>
                       <el-tab-pane label="搜索" name="second">
-                        <el-input placeholder="按ASIN搜索"></el-input>
-                        <el-table :data="proposalTableData" style="width: 100%" height="309">
-                          <el-table-column prop="proposal" label="商品" width="520" />
-                          <el-table-column prop="address" label="类型" />
-                          <el-table-column prop="operational" label="操作" />
+                        <el-input v-model="singleGoodsSearchInp" @change="singleGoodsSearchChaneged" placeholder="按ASIN搜索"></el-input>
+                        <el-table :data="searchTableData" style="width: 100%" height="309">
+                          <el-table-column prop="asin" label="商品" width="520">
+                            <template #default="{ row }">
+                              <div style="display: flex; align-items: center">
+                                <img :src="row.image_link" style="width: 40px; height: 40px; margin-right: 10px" />
+                                <span>{{ row.title }}</span>
+                              </div>
+                            </template>
+                          </el-table-column>
+                          <el-table-column prop="productTypes" label="类型">
+                            <template #default="scope">
+                              <div v-if="expand">扩展</div>
+                              <div v-if="accurate">精准</div>
+                            </template>
+                          </el-table-column>
+                          <el-table-column prop="operational" label="操作">
+                            <template #default="scope">
+                              <el-button class="button" text @click="addSingleSearch(scope)">添加</el-button>
+                            </template>
+                          </el-table-column>
                         </el-table>
                       </el-tab-pane>
                       <!-- TODO: 商品定向TextArea -->
@@ -667,7 +693,7 @@
                 </el-tabs>
               </div>
               <div style="width: 50%">
-                <el-card class="box-card" shadow="never">
+                <el-card class="box-card" shadow="never" style="border: none">
                   <template #header>
                     <div class="card-header">
                       <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ productOrientationTableData.length }}</span>
@@ -676,21 +702,45 @@
                   </template>
                   <div class="card-body">
                     <el-table
+                      height="460"
                       :data="productOrientationTableData"
                       style="width: 100%"
                       :header-row-style="changeKeyWordsTableHeader"
                       :header-cell-style="headerCellStyle">
                       <el-table-column prop="cna" label="分类 & 商品" width="300">
-                        <template #default="scope"> 分类: {{ scope.row.cna }} </template>
+                        <template #default="scope">
+                          <div v-if="scope.row.cna || scope.row.classification">
+                            分类: <span style="color: #000000">{{ scope.row.cna ? scope.row.cna : scope.row.classification }}</span>
+                          </div>
+                          <div v-if="scope.row.asin">
+                            {{ scope.row.asin ? scope.row.asin : '--' }}
+                          </div>
+                          <div v-if="scope.row.brand">
+                            品牌: <span style="color: #000000">{{ scope.row.brand }}</span>
+                          </div>
+                          <div v-if="scope.row.low_price || scope.row.high_price">
+                            品牌价格:
+                            <span style="color: #000000">
+                              {{ scope.row.low_price ? '$' + scope.row.low_price : '--' }} -
+                              {{ scope.row.high_price ? '$' + scope.row.high_price : '--' }}
+                            </span>
+                          </div>
+                          <div v-if="scope.row.low_rating || scope.row.high_rating">
+                            评分: <span style="color: #000000">{{ scope.row.low_rating }} - {{ scope.row.high_rating }}</span>
+                          </div>
+                          <div v-if="scope.row.deliveryText">
+                            配送: <span style="color: #000000">{{ scope.row.deliveryText }}</span>
+                          </div>
+                        </template>
                       </el-table-column>
                       <el-table-column prop="type" label="类型">
                         <template #default="scope">
-                          {{ scope.row.type ? 'scope.row.type' : '--' }}
+                          {{ scope.row.productTypeText ? scope.row.productTypeText : '--' }}
                         </template>
                       </el-table-column>
                       <el-table-column prop="bid" label="竞价">
                         <template #default="scope">
-                          <el-input-number v-model="scope.row.bid" :min="0" :max="1000000" :controls="false" size="small" />
+                          <el-input-number v-model="scope.row.bid" :min="0.02" :max="1000000" :controls="false" size="small" />
                         </template>
                       </el-table-column>
                       <el-table-column prop="operate" label="操作" width="60" align="right">
@@ -701,7 +751,9 @@
                     </el-table>
                   </div>
                 </el-card>
-                <div style="padding: 0 10px 0 10px; margin-top: -12px"></div>
+                <div style="display: flex; justify-content: space-around; margin-top: -8px">
+                  <el-button type="primary" plain @click="productTagetSave">保存</el-button>
+                </div>
               </div>
             </div>
           </el-form-item>
@@ -1545,17 +1597,6 @@ async function validatePrices(rule, value) {
   return Promise.resolve()
 }
 
-function dialogFormSubmit() {
-  dialogFormRef.value.validate((valid) => {
-    if (valid) {
-      console.log('表单提交')
-      visible.value = false
-    } else {
-      console.log('验证失败')
-    }
-  })
-}
-
 async function setProductOrientationData() {
   try {
     const resp = await request({
@@ -1654,30 +1695,237 @@ function delAllCna() {
 }
 
 function singleGoodsBidSelectChanged() {
-  if (singleGoodsBidSelect.value === 'defaultBid') {
+  if (singleGoodsBidSelect.value === 'defaultBid' || categoryBiddingType.value === 'defaultBid') {
     singleGoodsBidInput.value = ''
   }
 }
 
+let singleGoodsSearchInp = ref('')
+let searchTableData = ref([])
+function setSearchTableData(asin = '', sku = '') {
+  return request({
+    url: '/api/sellers/listings/our/',
+    method: 'GET',
+    params: {
+      profile_id: profile.value.profile_id,
+      asin,
+      sku,
+    },
+  })
+    .then((resp) => {
+      searchTableData.value = resp.data
+      productOrientationLoading.value = false
+    })
+    .catch((error) => {
+      console.error('Error fetching data:', error)
+      productOrientationLoading.value = false
+    })
+}
+function singleGoodsSearchChaneged() {
+  productOrientationLoading.value = true
+  setSearchTableData()
+}
+function addSingleSearch(scope) {
+  console.log('🚀 ~ addSingleSearch ~ scope-->>', scope);
+
+  const typesToAdd = [];
+  if (expand.value) {
+    typesToAdd.push('ASIN_EXPANDED_FROM');
+  }
+  if (accurate.value) {
+    typesToAdd.push('ASIN_SAME_AS');
+  }
+  const productTypeMap = {
+    ASIN_EXPANDED_FROM: '扩展',
+    ASIN_SAME_AS: '精确',
+  };
+
+  typesToAdd.forEach((productType) => {
+    const isAlreadyAdded = productOrientationTableData.value.some(item => item.sku === scope.row.sku && item.productType === productType);
+
+    let bidValue = null;
+    // 根据 categoryBiddingType.value 的值设置 bidValue
+    if (categoryBiddingType.value === 'defaultBid') {
+      bidValue = adGroupRuleForm.defaultBidInp;
+    } else if (categoryBiddingType.value === 'customBid') {
+      bidValue = singleGoodsBidInput.value;
+    }
+
+    if (!isAlreadyAdded) {
+      const newData = {
+        type: 'p',
+        asin: scope.row.asin,
+        sku: scope.row.sku,
+        productType: productType,
+        productTypeText: productTypeMap[productType],
+        bid: bidValue, // 添加 bid 值
+      };
+      productOrientationTableData.value.push(newData);
+    } else {
+      console.log(`${productType} item is already added.`);
+    }
+  });
+}
+
+
+let selectedLabels = ref([]) // 选中的label数组
+function dialogSelectChange(event) {
+  console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
+
+  // 使用 map 来转换每个选中项的 value 为其对应的 label
+  selectedLabels.value = event.map((selectedValue) => {
+    const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
+    return selectedOption ? selectedOption.label : ''
+  })
+
+  console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
+}
+
+let refineItem = ref([])
+// 细化按钮功能
 function refine(data) {
   console.log('🚀 ~ refine ~ data-->>', data)
   commodityCount.value = []
   dialogTitle.value = data.cna
   categoryId.value = data.cid
+  refineItem.value.push(data)
   visible.value = true
   dialogSelectLoading.value = true
   setDialogOption()
 }
+// 弹框提交功能
+function dialogFormSubmit() {
+  dialogFormRef.value.validate((valid) => {
+    if (valid) {
+      console.log('表单提交')
+      visible.value = false
+      const dialogClassification = dialogTitle.value
+      const dialogPrices_low = dialogForm.prices.lowest
+      const dialogPrices_high = dialogForm.prices.highest
+      const dialogStartRating = dialogForm.starRating
+      const ratingLow = dialogStartRating[0]
+      const ratingHigh = dialogStartRating[1]
+      const dialogDelivery = dialogForm.delivery
+      console.log('🚀 ~ dialogFormRef.value.validate ~ dialogDelivery-->>', dialogDelivery)
+      const deliveryMap = {
+        all: '所有',
+        eligible: '具备Prime资格',
+        diseligible: '不具备Prime资格',
+      }
+
+      let bidValue = null;
+      // 根据 categoryBiddingType.value 的值设置 bidValue
+      if (categoryBiddingType.value === 'defaultBid') {
+        bidValue = adGroupRuleForm.defaultBidInp;
+      } else if (categoryBiddingType.value === 'customBid') {
+        bidValue = singleGoodsBidInput.value;
+      }
+
+      selectedLabels.value.forEach((brandLabel) => {
+        // 查找与当前 brandLabel 相对应的选项
+        const selectedOption = dialogForm.dialogOptions.find((option) => option.label === brandLabel)
+        // 获取对应的 brandId,如果没有找到则默认为空
+        const brandId = selectedOption ? selectedOption.value : ''
+        const refineObj = {
+          type: 'c',
+          classification: dialogClassification,
+          classificationId: categoryId.value,
+          brand: brandLabel,
+          brandId: brandId, // 使用找到的 brandId
+          bid: bidValue, // 添加 bid 值
+          low_price: dialogPrices_low,
+          high_price: dialogPrices_high,
+          low_rating: ratingLow,
+          high_rating: ratingHigh,
+          delivery: dialogDelivery,
+          deliveryText: deliveryMap[dialogDelivery],
+        }
+        console.log('🚀 ~ dialogFormRef.value.validate ~ refineObj-->>', refineObj)
+        productOrientationTableData.value.push(refineObj)
+      })
+    } else {
+      console.log('验证失败')
+    }
+  })
+}
 
+// 定向按钮功能
 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;
+  // 根据 categoryBiddingType.value 的值设置 bidValue
+  if (categoryBiddingType.value === 'defaultBid') {
+    bidValue = adGroupRuleForm.defaultBidInp;
+  } else if (categoryBiddingType.value === 'customBid') {
+    bidValue = singleGoodsBidInput.value;
+  }
 
   if (!exists) {
-    productOrientationTableData.value.push(data)
+    const newData = {
+      type: 'c',
+      classification: data.cna,
+      classificationId: data.cid,
+      bid: bidValue, // 将 bid 值添加到新数据中
+    };
+    productOrientationTableData.value.push(newData);
   }
 }
 
+
+
+let productTargetBidList = ref([])
+async function productTagetSave() {
+  console.log('tableData', productOrientationTableData.value)
+  // 检查是否存在 bid 为空的行
+  const hasEmptyBid = productOrientationTableData.value.some(row => row.bid == null || row.bid === '')
+  // 直接返回,不继续执行
+  if (hasEmptyBid) {
+    console.log('存在空的 bid,不发送请求')
+    ElMessage.error('存在空的 bid,无法创建商品!')
+    return
+  }
+  productOrientationTableData.value.forEach((row) => {
+    productTargetBidList.value.push(row.bid)
+  })
+  console.log('productTargetBidList', productTargetBidList.value)
+  productOrientationLoading.value = true
+  try {
+    const requestData = {
+      profile_id: profile.value.profile_id,
+      adGroupId: respAdGroupId.value,
+      campaignId: respCampaignId.value,
+      expressionList: productOrientationTableData.value,
+      state: "PAUSED"
+    }
+    const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
+    const resp = await request({
+      url: '/api/ad_manage/sptargets/manual/create/',
+      method: 'POST',
+      data: filteredRequestData,
+    })
+
+    console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
+    productOrientationLoading.value = false
+
+    if (respAdGroupId.value) {
+      ElMessage({
+        message: '商品创建成功',
+        type: 'success',
+      })
+    } else {
+      ElMessage.error('商品创建失败!')
+    }
+  } catch (error) {
+    console.error('请求失败:', error)
+  }
+
+  // 清空表格和 bid 列表
+  productOrientationTableData.value = []
+  productTargetBidList.value = []
+}
+
 // ------------------------------------------关键词定向模块------------------------------------------
 const bidType = ref('customBid')
 const bidTypeOptions = [
@@ -1915,7 +2163,7 @@ function handleNegGoodsTabs(tab: TabsPaneContext, event: Event) {
 
 async function negativeGoodsSave() {
   console.log(addedNegetiveTableData.value)
-  const asinList = addedNegetiveTableData.value.map(item => item.asin)
+  const asinList = addedNegetiveTableData.value.map((item) => item.asin)
   console.log('🚀 ~ negativeGoodsSave ~ asinList-->>', asinList)
   negativeGoodsLoading.value = true
   console.log('addedNegetiveTableData', addedNegetiveTableData.value)
@@ -1925,7 +2173,7 @@ async function negativeGoodsSave() {
       campaignId: respCampaignId.value,
       adGroupId: respAdGroupId.value,
       asinList: asinList,
-      matchType: "ASIN_SAME_AS",
+      matchType: 'ASIN_SAME_AS',
       state: 'PAUSED',
     }
     const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))