Browse Source

Merge branch 'wang' into test

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

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

@@ -16,7 +16,7 @@
         status-icon>
         <div class="flex-between">
           <el-form-item label="广告活动名称" prop="campaignName" style="width: 48%">
-            <el-input v-model="campaignRuleForm.campaignName" />
+            <el-input v-model="campaignRuleForm.campaignName" placeholder="请输入广告活动名称" />
           </el-form-item>
           <el-form-item label="广告组合" prop="adMix" style="width: 48%">
             <el-select v-model="campaignRuleForm.adMix" placeholder="请选择" style="width: 100%">
@@ -78,13 +78,16 @@
         </el-form-item>
         <div style="width: 55%" v-if="campaignRuleForm.isBid == false">
           <el-card shadow="never" body-style="padding: 10px 10px 5px 10px;">
-            <div style="margin-bottom: 10px; font-weight: 500">展示位置出价调整</div>
+            <div style="margin-bottom: 10px; font-weight: 500">
+              <span style="color:#f56c6c;margin-right: 4px;">*</span>
+              展示位置出价调整
+            </div>
             <div style="display: flex; align-items: center">
               <div class="left">
                 <div class="title">商品页面</div>
                 <div class="tip">产品详情页面为顾客提供在亚马逊所售卖商品的详情信息</div>
               </div>
-              <el-form-item prop="commodityPage" style="margin-bottom: 0px !important; width: 37%">
+              <el-form-item prop="commodityPage" style="margin-bottom: 0px !important; width: 37%;">
                 <el-input v-model="campaignRuleForm.commodityPage" maxlength="3" placeholder="-99 ~ 900">
                   <template #append>%</template>
                 </el-input>
@@ -124,13 +127,12 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, reactive, ref, watch, defineEmits } from 'vue'
 import type { FormInstance, FormRules } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
-import { useRouter, useRoute } from 'vue-router'
+import { defineEmits, onMounted, reactive, ref, watch } from 'vue'
+import { getAdMixSelect, getBrands, postCampaignsData } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
-import { postCampaignsData, getAdMixSelect, getBrands } from '../api/index'
 import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
@@ -152,7 +154,7 @@ interface campaignRuleForm {
   firstPage: string
 }
 const campaignRuleForm = reactive<campaignRuleForm>({
-  campaignName: 'AiTestW10',
+  campaignName: '',
   adMix: '',
   startDate: '',
   endDate: '',
@@ -215,7 +217,7 @@ async function getBrandOption() {
 }
 
 const adMixOptions = ref([])
-async function buildAdMix() {
+async function getAdMix() {
   try {
     const response = await getAdMixSelect()
     adMixOptions.value = response.data.map((option) => {
@@ -230,8 +232,11 @@ async function buildAdMix() {
 }
 
 const campaignLoading = ref(false)
+const prevCampaignId = ref('')
+const prevCampaignName = ref('')
 const respCampaignId = ref('')
 const respCampaignName = ref('')
+
 async function createCampaigns() {
   campaignLoading.value = true
 
@@ -258,18 +263,31 @@ async function createCampaigns() {
 
   try {
     const response = await postCampaignsData(campaignData)
+
     respCampaignId.value = response.data.campaignId
     respCampaignName.value = response.data.campaignName
 
     if (response.data.campaignId) {
+      // 如果创建成功,更新 respCampaignId 和 respCampaignName,同时保留上一次成功的值
+      prevCampaignId.value = respCampaignId.value
+      prevCampaignName.value = respCampaignName.value
+      respCampaignId.value = response.data.campaignId
+      respCampaignName.value = response.data.campaignName
       ElMessage({ message: '广告活动创建成功', type: 'success' })
     } else {
+      // 如果创建失败,检查是否有上一次成功的值,如果有就保留
+      if (prevCampaignId.value) {
+        respCampaignId.value = prevCampaignId.value
+        respCampaignName.value = prevCampaignName.value
+      }
       ElMessage.error('广告活动创建失败!')
     }
   } catch (error) {
     console.error('请求失败:', error)
   } finally {
     campaignLoading.value = false
+    console.log('prevCampaignId.value', prevCampaignId.value)
+    console.log('respCampaignId.value', respCampaignId.value)
   }
 }
 
@@ -292,7 +310,7 @@ watch(
 )
 
 onMounted(() => {
-  buildAdMix()
+  getAdMix()
   getBrandOption()
 })
 </script>

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

@@ -9,7 +9,8 @@
         <el-radio-group v-model="adFormatRadio" style="display: flex; justify-content: space-between">
           <el-radio class="ad-format-radio" label="productSet" border>
             <div style="text-align: center; color: #333333">商品集</div>
-            <div style="background-color: #1e2128; width: 200px; height: 200px; margin: 0 auto"></div>
+            <img src="src/views/adManage/sb/campaigns/CreateCampaigns/img/img_1.jpg" class="img-style">
+            <!-- <div style="background-color: #1e2128; width: 200px; height: 200px; margin: 0 auto"></div> -->
             <div style="padding: 5px 0 10px 0; color: #333333; font-weight: 400">使用图片将流量引导至商品详情页面, 以推广多件商品</div>
           </el-radio>
           <el-radio class="ad-format-radio" label="focus" border>
@@ -130,14 +131,14 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, defineEmits, watch, onMounted, nextTick } from 'vue'
 import type { FormInstance, FormRules } from 'element-plus'
+import { storeToRefs } from 'pinia'
+import { defineEmits, onMounted, reactive, ref, watch } from 'vue'
+import { getBrands, getStoreurl } from '../api/index'
 import ProductSetCommodity from '../component/ProductSetCommodity.vue'
 import VideoCommodity from '../component/VideoCommodity.vue'
-import { getBrands, getStoreurl } from '../api/index'
-import emitter from '/@/utils/emitter'
-import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
+import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -361,4 +362,9 @@ onMounted(() => {
 ::v-deep(.land-Page label.el-radio.is-bordered.is-checked.el-radio--default) {
   background: #f5f7fe;
 }
+.img-style {
+  width: 200px;
+  height: 200px;
+  margin: 0 auto;
+}
 </style>

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

@@ -15,7 +15,7 @@
         :size="formSize"
         status-icon>
         <el-form-item label="广告组名称" prop="groupName">
-          <el-input v-model="groupRuleForm.groupName" style="width: 600px" />
+          <el-input v-model="groupRuleForm.groupName" style="width: 600px" placeholder="请输入广告组名称" />
           <el-button type="primary" plain :disabled="!respCampaignId" @click="submitGroupForm(groupRuleFormRef)" style="margin-left: 30px">保存</el-button>
         </el-form-item>
       </el-form>
@@ -44,7 +44,7 @@ interface groupRuleForm {
   groupName: string
 }
 const groupRuleForm = reactive<groupRuleForm>({
-  groupName: 'AiTestGroupW04',
+  groupName: '',
 })
 const groupRules = reactive<FormRules<groupRuleForm>>({
   groupName: [{ required: true, message: '请输入广告活动', trigger: 'blur' }],

+ 161 - 43
src/views/adManage/sb/campaigns/CreateCampaigns/component/KeywordTarget.vue

@@ -1,24 +1,24 @@
 <template>
   <div style="width: 100%; margin-top: 20px">
     <el-divider content-position="left">
-      <span style="font-size: 18px;font-weight: 700;">关键词定向</span>
+      <span style="font-size: 18px; font-weight: 700">关键词定向</span>
     </el-divider>
-    <div style="width: 100%; height: 600px; margin-top: 10px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px">
+    <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px" v-loading="keywordsLoading">
       <div style="width: 50%; border-right: 1px solid #e5e7eb">
         <el-tabs v-model="keyWordsTabs" class="demo-tabs">
-          <div style="margin: 8px 10px 10px 10px">
-            <p style=" margin-bottom: -5px; font-weight: 500; color: #616266">竞价:</p>
-            <div style="display: flex; align-items: center; margin-left:-7px">
+          <div style="margin: 8px">
+            <div style="display: flex; align-items: center">
+              <div style="min-width: 40px; margin-left: 8px; font-weight: 500; color: #616266">竞价:</div>
               <el-select v-model="bidType" class="m-2" placeholder="Select" style="width: 450px">
-                <el-option v-for="item in bidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+                <el-option v-for="item in bidTypeOptions" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" />
               </el-select>
-              <el-input v-model="bidInput" placeholder="Please input">
+              <el-input v-model="bidInput" :disabled="!(bidType == 'customBid')" placeholder="Please input">
                 <template #prepend>$</template>
               </el-input>
             </div>
-            <div style="display: flex; align-items: center;">
-              <span style="margin: 0 10px 0 0; font-weight: 500; color: #616266">匹配类型: </span>
-              <el-checkbox v-model="widelyType" label="广泛" />
+            <div style="display: flex; align-items: center">
+              <span style="margin: 0 10px 0 8px; font-weight: 500; color: #616266">匹配类型: </span>
+              <el-checkbox v-model="broadType" label="广泛" />
               <el-checkbox v-model="phraseType" label="词组" />
               <el-checkbox v-model="exactType" label="精确" />
             </div>
@@ -26,18 +26,20 @@
           <el-tab-pane label="建议" name="first">
             <el-table
               height="425"
-              v-loading="loading"
-              :data="keywordsTableData"
+              style="width: 100%; padding-left: 5px"
+              :data="keyWordsTableData"
               :header-cell-style="headerCellStyle"
               :header-row-style="changeKeyWordsTableHeader">
               <el-table-column prop="asin" label="关键词"> </el-table-column>
-              <el-table-column prop="name" label="匹配类型"> </el-table-column>
-              <el-table-column prop="name" label="建议出价" width="120"> </el-table-column>
+              <el-table-column prop="matchType" label="匹配类型"> </el-table-column>
+              <el-table-column prop="adviceBid" label="建议出价" width="120"> </el-table-column>
             </el-table>
           </el-tab-pane>
           <el-tab-pane label="输入" name="second">
-            <el-input v-model="keyWordsTextarea" :rows="10" type="textarea" disabled="true" style="padding-left: 5px" />
-            <!-- <el-button type="primary" size="small" text bg @click="addKeyWords">添加</el-button> -->
+            <el-input v-model="keyWordsTextarea" :rows="10" type="textarea" style="padding-left: 5px" />
+            <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
+              <el-button type="primary" text bg @click="addKeyWords">添加</el-button>
+            </div>
           </el-tab-pane>
         </el-tabs>
       </div>
@@ -45,8 +47,10 @@
         <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">已添加: {{  }}</span>
-              <el-button class="button" text bg @click="delAllKeyWords">全部删除</el-button>
+              <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedKeyWordsTableData.length }}</span>
+              <span style="color: #529b2e">成功: {{ successCount }}</span>
+              <span style="color: #c45656">失败: {{ errorCount }}</span>
+              <el-button class="button" type="danger" text bg @click="delAllKeyWords">全部删除</el-button>
             </div>
           </template>
           <div class="card-body" body-style="padding-bottom: -20px;">
@@ -56,14 +60,27 @@
               :header-row-style="changeKeyWordsTableHeader"
               :header-cell-style="headerCellStyle">
               <el-table-column prop="keyword" label="关键词" width="auto" />
-              <el-table-column prop="bid" label="出价" />
-              <el-table-column prop="suggestBid" label="建议出价" />
-              <el-table-column prop="operate" label="操作" width="60" align="right" />
+              <el-table-column prop="matchType" label="匹配类型" />
+              <el-table-column prop="bid" label="出价">
+                <template #default="scope">
+                  <el-input v-model="scope.row.bid" placeholder="Please input bid" />
+                </template>
+              </el-table-column>
+              <el-table-column prop="suggestBid" label="建议出价" align="center">
+                <template #default="{ row }">
+                  <div>{{ row.adviceBid ? row.adviceBid : '--' }}</div>
+                </template>
+              </el-table-column>
+              <el-table-column prop="operate" label="操作" width="60" align="right">
+                <template #default="scope">
+                  <el-button type="danger" size="small" link @click="delSingleKeyWord(scope)">删除</el-button>
+                </template>
+              </el-table-column>
             </el-table>
           </div>
         </el-card>
         <div style="display: flex; justify-content: space-around; padding-top: 0px">
-          <el-button type="primary" plain :disabled="!addedKeyWordsTableData.length">保存</el-button>
+          <el-button type="primary" plain @click="keyWordsSave" :disabled="!addedKeyWordsTableData.length">保存</el-button>
         </div>
       </div>
     </div>
@@ -71,26 +88,25 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
-
+import { ElMessage } from 'element-plus'
+import { storeToRefs } from 'pinia'
+import { Ref, inject, ref } from 'vue'
+import { useShopInfo } from '/@/stores/shopInfo'
+import { request } from '/@/utils/service'
 
+const respCampaignId = inject<Ref>('respCampaignId')
+const respAdGroupId = inject<Ref>('respAdGroupId')
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
 
 const keyWordsTabs = ref('first')
-const bidInput = ref('0.75')
-const keywordsTableData = ref([]) // 关键词定向左侧表格数据
-const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
-const keyWordsTextarea = ref('')
-
-let widelyType = ref(true)
-let phraseType = ref(true)
-let exactType = ref(true)
-
-const loading = ref(false)
 const bidType = ref('customBid')
+const keywordsLoading = ref(false)
 const bidTypeOptions = [
   {
     value: 'suggestBid',
     label: '建议出价',
+    disabled: true,
   },
   {
     value: 'customBid',
@@ -99,27 +115,130 @@ const bidTypeOptions = [
   {
     value: 'defaultBid',
     label: '默认出价',
+    disabled: true,
   },
 ]
 
+const bidInput = ref('0.75')
+const keyWordsTableData = ref([]) // 关键词定向左侧表格数据
+const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
+const keyWordsTextarea = ref('')
+let broadType = ref(true)
+let phraseType = ref(true)
+let exactType = ref(true)
+const MATCH_TYPE = {
+  broad: '广泛',
+  phrase: '词组',
+  exact: '精确',
+}
+const MATCH_TYPE_MAP = {
+  广泛: 'broad',
+  词组: 'phrase',
+  精确: 'exact',
+}
+const successCount = ref('')
+const errorCount = ref('')
 
+function addKeyWords() {
+  const trimmedText = keyWordsTextarea.value.trim()
+  const items = trimmedText.split(/,|\n/)
+
+  items.forEach((item) => {
+    const trimmedItem = item.trim()
+    if (trimmedItem) {
+      if (broadType.value) {
+        addKeyWordEntry(trimmedItem, MATCH_TYPE.broad)
+      }
+      if (phraseType.value) {
+        addKeyWordEntry(trimmedItem, MATCH_TYPE.phrase)
+      }
+      if (exactType.value) {
+        addKeyWordEntry(trimmedItem, MATCH_TYPE.exact)
+      }
+    } else {
+      ElMessage({
+        message: '有空项目,未被添加到列表中',
+        type: 'warning',
+      })
+    }
+  })
+  keyWordsTextarea.value = ''
+}
+
+function addKeyWordEntry(keyword, matchType) {
+  let keyWordEntry = {
+    keyword: keyword,
+    matchType: matchType,
+    bid: bidInput.value,
+  }
+
+  if (!addedKeyWordsTableData.value.some((n) => n.keyword === keyWordEntry.keyword && n.matchType === keyWordEntry.matchType)) {
+    addedKeyWordsTableData.value.push(keyWordEntry)
+  } else {
+    ElMessage({
+      message: `关键词 ${keyword} (${matchType}) 已存在,未被添加到列表中`,
+      type: 'warning',
+    })
+  }
+}
+
+function delSingleKeyWord(scope) {
+  const index = addedKeyWordsTableData.value.findIndex((item) => item.keyword === scope.row.keyword && item.matchType === scope.row.matchType)
+  if (index !== -1) {
+    addedKeyWordsTableData.value.splice(index, 1)
+  } else {
+    console.log('无效的索引,无法删除条目')
+  }
+}
 
 function delAllKeyWords() {
-  // 删除已经添加的关键词
   addedKeyWordsTableData.value = []
 }
 
-function addKeyWords() {
-  // 添加关键词
-  if (keyWordsTextarea.value) {
-    addedKeyWordsTableData.value.push({
-      keyWords: keyWordsTextarea.value,
+// TODO: 必须要创建了商品之后才能创建关键词定向
+async function keyWordsSave() {
+  keywordsLoading.value = true
+  successCount.value = ''
+  errorCount.value = ''
+
+  const keywordList = addedKeyWordsTableData.value.map((kw) => ({
+    keywordText: kw.keyword,
+    bid: kw.bid,
+    matchType: MATCH_TYPE_MAP[kw.matchType],
+  }))
+
+  const requestData = {
+    profile_id: profile.value.profile_id,
+    campaignId: respCampaignId.value,
+    adGroupId: respAdGroupId.value,
+    keywordlist: keywordList,
+  }
+  const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
+  try {
+    const resp = await request({
+      url: '/api/ad_manage/sbtargets/add/keywords/',
+      method: 'POST',
+      data: filteredRequestData,
     })
-    keyWordsTextarea.value = ''
+    if (resp.data.success.length !== 0) {
+      ElMessage({
+        message: '关键词创建成功',
+        type: 'success',
+      })
+      successCount.value = resp.data.success.length
+      errorCount.value = resp.data.error.length
+      delAllKeyWords()
+    } else {
+      ElMessage.error('关键词创建失败!')
+    }
+  } catch (error) {
+    console.error('请求失败:', error)
+  } finally {
+    keywordsLoading.value = false
   }
 }
 
-const headerCellStyle = (args) => {
+function headerCellStyle(args) {
   if (args.rowIndex === 0) {
     return {
       backgroundColor: 'rgba(245, 245, 245, 0.9)',
@@ -135,7 +254,6 @@ function changeKeyWordsTableHeader(args) {
     }
   }
 }
-
 </script>
 
 <style lang="scss" scoped>

+ 14 - 11
src/views/adManage/sb/campaigns/CreateCampaigns/component/ProductSetCreativity1.vue

@@ -98,7 +98,7 @@
                       <div>
                         <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
                         <span class="el-upload-list__item-actions">
-                          <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
+                          <span class="el-upload-list__item-preview" @click="customPictureCardPreview(file)">
                             <el-icon><zoom-in /></el-icon>
                           </span>
                           <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
@@ -121,7 +121,7 @@
                     </template>
                   </el-upload>
                   <!-- 预览弹窗 -->
-                  <el-dialog v-model="dialogVisible">
+                  <el-dialog v-model="customDialogVisible">
                     <img w-full :src="dialogImageUrl" alt="Preview Image" />
                   </el-dialog>
                 </el-collapse-item>
@@ -277,16 +277,14 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref, inject, Ref, watch, computed, onMounted, onUnmounted, nextTick } 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, uploadFile, checkAsset, postProductset } from '../api/index'
-import emitter from '/@/utils/emitter'
+import { Delete, Picture, Plus, Search, ZoomIn } from '@element-plus/icons-vue'
+import type { FormInstance, FormRules, UploadFile, UploadProps } from 'element-plus'
+import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
+import { Ref, computed, inject, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
+import { checkAsset, getAssets, getCommodityCard, getLifeStyleAssets, getPageAsins, postProductset, uploadFile } from '../api/index'
 import { useShopInfo } from '/@/stores/shopInfo'
-import axios from 'axios'
+import emitter from '/@/utils/emitter'
 
 const respAdGroupId = inject<Ref>('respAdGroupId')
 const shopInfo = useShopInfo()
@@ -334,6 +332,7 @@ const handleChange = (val: string[]) => {
 // 图片上传相关
 const dialogImageUrl = ref('')
 const dialogVisible = ref(false)
+const customDialogVisible = ref(false)
 const disabled = ref(false)
 const pictureList = ref([])
 const fileList = ref([])
@@ -373,11 +372,15 @@ function handleCustomRemove(file: UploadFile) {
 }
 
 function handlePictureCardPreview(file: UploadFile) {
-  handleUpload(file)
   dialogImageUrl.value = file.url!
   dialogVisible.value = true
 }
 
+function customPictureCardPreview(file: UploadFile) {
+  dialogImageUrl.value = file.url!
+  customDialogVisible.value = true
+}
+
 // 自定义文件上传
 const customFileList = ref([])
 const customUpLoading = ref(false)

BIN
src/views/adManage/sb/campaigns/CreateCampaigns/img/img_1.jpg


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

@@ -17,12 +17,11 @@
             <hr />
             <br />
             <el-form-item label="广告活动名称" prop="name" style="width: 350px">
-              <el-input v-model="campaignRuleForm.name" />
+              <el-input v-model="campaignRuleForm.name" placeholder="请输入广告活动名称" />
             </el-form-item>
             <el-form-item label="广告组合" prop="adMix">
-              <el-select v-model="campaignRuleForm.adMix" placeholder="请选择">
-                <el-option label="Zone one" value="shanghai" />
-                <el-option label="Zone two" value="beijing" />
+              <el-select v-model="campaignRuleForm.adMix" clearable placeholder="请选择">
+                <el-option v-for="item in adMixOptions" :key="item.portfolioId" :label="item.name" :value="item.portfolioId" />
               </el-select>
             </el-form-item>
             <el-form-item prop="startDate" label="开始日期" style="width: 350px">
@@ -120,7 +119,7 @@
             <br />
             <el-form ref="adGroupRuleFormRef" :model="adGroupRuleForm" :rules="adGroupRules">
               <el-form-item required label="广告组名称" prop="adGroupName" style="width: 350px; margin-top: 20px">
-                <el-input v-model="adGroupRuleForm.adGroupName" />
+                <el-input v-model="adGroupRuleForm.adGroupName"  placeholder="请输入广告组名称"/>
               </el-form-item>
               <el-form-item required label="默认竞价" prop="defaultBidInp">
                 <el-input v-model="adGroupRuleForm.defaultBidInp" minlength="3" maxlength="4" style="width: 200px">
@@ -401,7 +400,7 @@
           </div>
           <hr v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'" />
           <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'">
-            <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px">
+            <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px" v-loading="keywordsLoading">
               <div style="width: 50%; border-right: 1px solid #e5e7eb">
                 <el-tabs v-model="keyWordsTabs" class="demo-tabs" @tab-click="handleGoodsTabs">
                   <div style="margin: 8px">
@@ -950,7 +949,7 @@ interface CampaignForm {
   other: string
 }
 const campaignRuleForm = reactive<CampaignForm>({
-  name: 'aitest02campaign_wxc',
+  name: '',
   adMix: '',
   count: '',
   startDate: '',
@@ -1014,6 +1013,7 @@ async function createCampaigns() {
       t_percentage: campaignRuleForm.placeBid,
       p_percentage: campaignRuleForm.firstPage,
       r_percentage: campaignRuleForm.other,
+      portfolioId: campaignRuleForm.adMix,
     }
 
     // 过滤掉 undefined 或空的可选字段
@@ -1047,6 +1047,19 @@ async function createCampaigns() {
   }
 }
 
+const adMixOptions = ref([])
+async function getAdMix() {
+  try { 
+    const resp = await request({
+      url: '/api/ad_manage/portfolios/select_list',
+      method: 'GET',
+    })
+    adMixOptions.value = resp.data
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
+
 async function submitCampaignForm(formEl: FormInstance | undefined) {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
@@ -1933,6 +1946,7 @@ async function productTagetSave() {
 
 // ------------------------------------------关键词定向模块------------------------------------------
 const bidType = ref('customBid')
+const keywordsLoading = ref(false)
 const bidTypeOptions = [
   {
     value: 'suggestBid',
@@ -2047,6 +2061,7 @@ function delAllKeyWords() {
 }
 
 async function keyWordsSave() {
+  keywordsLoading.value = true
   successCount.value = ''
   errorCount.value = ''
   const keywordList = addedKeyWordsTableData.value.map((kw) => ({
@@ -2081,6 +2096,8 @@ async function keyWordsSave() {
     }
   } catch (error) {
     console.error('请求失败:', error)
+  } finally {
+    keywordsLoading.value = false
   }
 }
 
@@ -2421,6 +2438,7 @@ function changeKeyWordsTableHeader(args) {
 
 onMounted(() => {
   setTableData()
+  getAdMix()
   // const myTest = route.query
   // console.log('myTest', myTest)
 })