فهرست منبع

✨ feat:

SB-新建广告活动下视频的商品详情页创建商品创意完成; SD-新建广告活动下创建广告活动模板完成
WanGxC 1 سال پیش
والد
کامیت
e1ebed88ec

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

@@ -77,4 +77,20 @@ export function getCommodityCard(query) {
       method: 'get',
       params: query
   })
+}
+
+export function getVideoAssets(query) {
+  return request({
+      url: '/api/ad_manage/sb/assets/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function videoDetailCreate(obj) {
+  return request({
+      url: '/api/ad_manage/sbads/video/create/',
+      method: 'post',
+      data: obj,
+  })
 }

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

@@ -63,7 +63,7 @@
         </div>
         <el-form-item label="品牌" prop="brand" required style="width: 48%">
           <el-select v-model="campaignRuleForm.brand" placeholder="请选择" style="width: 100%">
-            <el-option v-for="item in brandOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option v-for="item in brandOptions" :key="item.brandId" :label="item.brandRegistryName" :value="item.brandEntityId" />
           </el-select>
         </el-form-item>
         <div style="font-weight: 700; padding-bottom: 18px">
@@ -76,7 +76,46 @@
             <span style="margin-left: 10px; color: #88909b">允许亚马逊自动优化搜索结果首页以外的广告位竞价</span>
           </el-form-item>
         </el-form-item>
-        <el-form-item style="margin-left: 48%; margin-bottom: -10px">
+        <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="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-input v-model="campaignRuleForm.commodityPage" maxlength="3" placeholder="-99 ~ 900">
+                  <template #append>%</template>
+                </el-input>
+              </el-form-item>
+            </div>
+            <div style="display: flex; align-items: center; margin-top: 10px">
+              <div class="left">
+                <div class="title">搜索结果顶部(首页)</div>
+                <div class="tip">亚马逊首页 http://www.amazon.com</div>
+              </div>
+              <el-form-item prop="firstPage" style="margin-bottom: 0px !important; width: 37%">
+                <el-input v-model="campaignRuleForm.firstPage" maxlength="3" placeholder="-99 ~ 900" style="width: 100%">
+                  <template #append>%</template>
+                </el-input>
+              </el-form-item>
+            </div>
+            <div style="display: flex; align-items: center; margin-top: 10px">
+              <div class="left">
+                <div class="title">搜索结果的其余位置</div>
+                <div class="tip">其他位置集合, 例如搜索页</div>
+              </div>
+              <el-form-item prop="otherPlace" style="margin-bottom: 0px !important; width: 37%">
+                <el-input v-model="campaignRuleForm.otherPlace" maxlength="3" placeholder="-99 ~ 900">
+                  <template #append>%</template>
+                </el-input>
+              </el-form-item>
+            </div>
+            <div style="color: #8d9095; padding-left: 60%; margin-top: 10px">示例: $5.00 竞价降低 40% 将变为 $3.00</div>
+          </el-card>
+        </div>
+        <el-form-item style="margin: 20px 0 -10px 48%">
           <el-button type="primary" plain @click="submitCampaignForm(campaignRuleFormRef)">保存</el-button>
         </el-form-item>
       </el-form>
@@ -91,7 +130,7 @@ import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
 import { useRouter, useRoute } from 'vue-router'
 import { useShopInfo } from '/@/stores/shopInfo'
-import { postCampaignsData, getAdMixSelect } from '../api/index'
+import { postCampaignsData, getAdMixSelect, getBrands } from '../api/index'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -107,16 +146,22 @@ interface campaignRuleForm {
   frequency: string
   brand: string
   isBid: boolean
+  commodityPage: string
+  otherPlace: string
+  firstPage: string
 }
 const campaignRuleForm = reactive<campaignRuleForm>({
-  campaignName: 'AiTestW01',
+  campaignName: 'AiTestW10',
   adMix: '',
   startDate: '',
   endDate: '',
   budget: '',
-  frequency: 'daily',
-  brand: 'zosi',
+  frequency: 'DAILY',
+  brand: '',
   isBid: false,
+  commodityPage: '',
+  otherPlace: '',
+  firstPage: '',
 })
 const campaignRules = reactive<FormRules<campaignRuleForm>>({
   campaignName: [{ required: true, message: '请输入广告活动', trigger: 'blur' }],
@@ -125,10 +170,22 @@ const campaignRules = reactive<FormRules<campaignRuleForm>>({
     { required: true, message: '请输入预算', trigger: 'blur' },
     { pattern: /^(?:[1-9]\d{0,5}|1000000)(?:\.\d{1,2})?$/, message: '预算必须是1到1000000之间的数字,小数点后最多两位', trigger: 'blur' },
   ],
+  commodityPage: [
+    { required: true, message: '必填', trigger: 'blur' },
+    { pattern: /^-?(99|[1-8]?[0-9]?[0-9])$/, message: '请输入-99到900之间的数字', trigger: 'blur' },
+  ],
+  otherPlace: [
+    { required: true, message: '必填', trigger: 'blur' },
+    { pattern: /^-?(99|[1-8]?[0-9]?[0-9])$/, message: '请输入-99到900之间的数字', trigger: 'blur' },
+  ],
+  firstPage: [
+    { required: true, message: '必填', trigger: 'blur' },
+    { pattern: /^-?(99|[1-8]?[0-9]?[0-9])$/, message: '请输入-99到900之间的数字', trigger: 'blur' },
+  ],
 })
 const frequencyOptions = [
   {
-    value: 'daily',
+    value: 'DAILY',
     label: '每日',
   },
   {
@@ -137,12 +194,8 @@ const frequencyOptions = [
     disabled: true,
   },
 ]
-const brandOptions = [
-  {
-    value: 'zosi',
-    label: 'ZOSI',
-  },
-]
+const brandOptions = ref([])
+
 const submitCampaignForm = async (formEl: FormInstance | undefined) => {
   if (!formEl) return
   await formEl.validate((valid, fields) => {
@@ -155,6 +208,11 @@ const submitCampaignForm = async (formEl: FormInstance | undefined) => {
   })
 }
 
+async function getBrandOption() {
+  const response = await getBrands({ profile_id: profile.value.profile_id })
+  brandOptions.value = response.data
+}
+
 const adMixOptions = ref([])
 async function buildAdMix() {
   try {
@@ -175,30 +233,35 @@ const respCampaignId = ref('')
 const respCampaignName = ref('')
 async function createCampaigns() {
   campaignLoading.value = true
-  // 构建请求体
+
+  // 构建基础请求体
   const campaignData = {
     profile_id: profile.value.profile_id,
     budget: campaignRuleForm.budget,
     budgetType: campaignRuleForm.frequency,
     name: campaignRuleForm.campaignName,
+    brandEntityId: campaignRuleForm.brand,
     bidOptimization: campaignRuleForm.isBid,
     bidOptimizationStrategy: '',
     startDate: campaignRuleForm.startDate,
     endDate: campaignRuleForm.endDate,
+    smartDefault: 'MANUAL',
+    costType: 'CPC',
     goal: 'PAGE_VISIT',
-    state: 'PAUSED',
+    state: 'ENABLED',
+    ...(campaignRuleForm.firstPage && { h_percentage: campaignRuleForm.firstPage }),
+    ...(campaignRuleForm.commodityPage && { d_percentage: campaignRuleForm.commodityPage }),
+    ...(campaignRuleForm.otherPlace && { o_percentage: campaignRuleForm.otherPlace }),
+    ...(campaignRuleForm.adMix && { portfolioId: campaignRuleForm.adMix }),
   }
-  const filteredRequestData = Object.fromEntries(Object.entries(campaignData).filter(([_, v]) => v != null))
+
   try {
-    const response = await postCampaignsData(filteredRequestData)
+    const response = await postCampaignsData(campaignData)
     respCampaignId.value = response.data.campaignId
     respCampaignName.value = response.data.campaignName
-    console.log('🚀 ~ createCampaigns ~ response-->>', response)
+
     if (response.data.campaignId) {
-      ElMessage({
-        message: '广告活动创建成功',
-        type: 'success',
-      })
+      ElMessage({ message: '广告活动创建成功', type: 'success' })
     } else {
       ElMessage.error('广告活动创建失败!')
     }
@@ -222,6 +285,7 @@ watch([respCampaignId, respCampaignName], () => {
 
 onMounted(() => {
   buildAdMix()
+  getBrandOption()
 })
 </script>
 
@@ -230,4 +294,18 @@ onMounted(() => {
   display: flex;
   justify-content: space-between;
 }
+.left {
+  margin-right: 12px;
+  width: 60%;
+}
+.title {
+  font-size: 14px;
+  line-height: 20px;
+  color: #1d2129;
+}
+.tip {
+  font-size: 14px;
+  line-height: 20px;
+  color: #4e5969;
+}
 </style>

+ 35 - 27
src/views/adManage/sb/campaigns/CreateCampaigns/component/AdFormat.vue

@@ -53,12 +53,22 @@
                 status-icon>
                 <div style="display: flex; margin-top: 10px">
                   <el-form-item label="选择一个店铺" prop="shop" style="width: 48%; margin-right: 10px">
-                    <el-select v-model="ruleForm.shop" clearable style="width: 100%" @blur="validateField('shop')">
+                    <el-select
+                      v-model="ruleForm.shop"
+                      clearable
+                      style="width: 100%"
+                      @blur="validateField('shop')"
+                      :disabled="arrivalsRadio == 'newArrivals' || arrivalsRadio == 'productDetailsPage'">
                       <el-option v-for="item in shopOptions" :key="item.value" :label="item.label" :value="item.value" />
                     </el-select>
                   </el-form-item>
                   <el-form-item label="选择一个页面" prop="page" style="width: 48%">
-                    <el-select v-model="ruleForm.page" clearable style="width: 100%" @blur="validateField('page')">
+                    <el-select
+                      v-model="ruleForm.page"
+                      clearable
+                      style="width: 100%"
+                      @blur="validateField('page')"
+                      :disabled="arrivalsRadio == 'newArrivals' || arrivalsRadio == 'productDetailsPage'">
                       <el-option v-for="item in pageOptions" :key="item.storePageId" :label="item.storePageName" :value="item.storePageUrl" />
                     </el-select>
                   </el-form-item>
@@ -88,8 +98,12 @@
           size="default"
           status-icon>
           <el-form-item label="选择一个店铺" prop="focusShop">
-            <el-select v-model="flagshipStoreRuleForm2.focusShop" style="padding-top: 10px; margin-top: -15px; width: 500px">
-              <el-option v-for="item in focusShopOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-select
+              v-model="flagshipStoreRuleForm2.focusShop"
+              clearable
+              @blur="validateField2('focusShop')"
+              style="padding-top: 10px; margin-top: -15px; width: 500px">
+              <el-option v-for="item in focusShopOptions" :key="item.brandEntityId" :label="item.brandRegistryName" :value="item.brandId" />
             </el-select>
           </el-form-item>
         </el-form>
@@ -106,7 +120,9 @@
           <span style="font-size: 18px; padding-left: 5px">商品</span>
         </div>
         <ProductSetCommodity v-if="adFormatRadio === 'productSet' && arrivalsRadio === 'newArrivals'"></ProductSetCommodity>
-        <VideoCommodity v-if="adFormatRadio === 'video' && arrivalsRadio === 'productDetailsPage'"></VideoCommodity>
+        <VideoCommodity
+          @update-added-data="handleUpdateAddedData"
+          v-if="adFormatRadio === 'video' && arrivalsRadio === 'productDetailsPage'"></VideoCommodity>
       </div>
     </el-card>
   </div>
@@ -120,6 +136,7 @@ import VideoCommodity from '../component/VideoCommodity.vue'
 import { getBrands, getStoreurl } from '../api/index'
 import { storeToRefs } from 'pinia'
 import { useShopInfo } from '/@/stores/shopInfo'
+import { da } from 'element-plus/es/locale'
 
 const shopInfo = useShopInfo()
 const { profile } = storeToRefs(shopInfo)
@@ -136,12 +153,11 @@ const ruleForm = reactive<RuleForm>({
   page: '',
 })
 const rules = reactive<FormRules<RuleForm>>({
-  shop: [{ required: true, message:'请选择', trigger: 'change' }],
-  page: [{ required: true, message:'请选择', trigger: 'blur' }],
+  shop: [{ required: true, message: '请选择', trigger: 'change' }],
+  page: [{ required: true, message: '请选择', trigger: 'change' }],
 })
 const validateField = (fieldName) => {
-  ruleFormRef.value.validateField(fieldName, () => {
-  })
+  ruleFormRef.value.validateField(fieldName, () => {})
 }
 
 const flagshipStoreRuleFormRef2 = ref<FormInstance>()
@@ -152,21 +168,15 @@ const flagshipStoreRuleForm2 = reactive<flagshipStoreRuleForm2>({
   focusShop: '',
 })
 const flagshipStoreRules2 = reactive<FormRules<flagshipStoreRuleForm2>>({
-  focusShop: { required: true, message: 'xxx', trigger: 'blur' },
+  focusShop: [{ required: true, message: '请选择', trigger: 'change' }],
 })
+const validateField2 = (fieldName) => {
+  flagshipStoreRuleFormRef2.value.validateField(fieldName, () => {})
+}
 
 const shopOptions = ref([])
 const pageOptions = ref([])
-const focusShopOptions = [
-  {
-    value: '1',
-    label: '1',
-  },
-  {
-    value: '2',
-    label: '2',
-  },
-]
+const focusShopOptions = ref([])
 
 async function getShopOptions() {
   try {
@@ -177,6 +187,7 @@ async function getShopOptions() {
         label: 'ZOSI',
       }
     })
+    focusShopOptions.value = response.data
     shopOptions.value = shopOption
   } catch (error) {
     console.log('error:', error)
@@ -192,19 +203,16 @@ async function getPageOptions() {
   }
 }
 
-// watch(ruleForm.shop, async (newShopValue) => {
-//   if (newShopValue === 'ZOSI') {
-//     getPageOptions()
-//   }
-// })
-
 onMounted(() => {
   getShopOptions()
   // getPageOptions()
 })
 
-const emit = defineEmits(['update:adFormatRadio', 'update:arrivalsRadio', 'update:flagshipStoreShop', 'update:pageOptions'])
+const emit = defineEmits(['update:adFormatRadio', 'update:arrivalsRadio', 'update:flagshipStoreShop', 'update:pageOptions', 'update:addedTableData'])
 
+function handleUpdateAddedData(data) {
+  emit('update:addedTableData', data)
+}
 // 监听 adFormatRadio 的变化并触发事件
 watch(
   adFormatRadio,

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

@@ -16,7 +16,7 @@
         status-icon>
         <el-form-item label="广告组名称" prop="groupName">
           <el-input v-model="groupRuleForm.groupName" style="width: 600px" />
-          <el-button type="primary" plain :disabled="!respAdGroupId" @click="submitGroupForm(groupRuleFormRef)" style="margin-left: 30px">保存</el-button>
+          <el-button type="primary" plain :disabled="!respCampaignId" @click="submitGroupForm(groupRuleFormRef)" style="margin-left: 30px">保存</el-button>
         </el-form-item>
       </el-form>
     </el-card>
@@ -75,14 +75,11 @@ async function createGroups() {
   try {
     const response = await postGroupData(filteredRequestData)
     respAdGroupId.value = response.data.adGroupId
-    console.log('🚀 ~ createGroups ~ response-->>', response)
-    // if () {
-
-    // }
     ElMessage({
       message: '广告组创建成功',
       type: 'success',
     })
+    groupRuleForm.groupName = ''
   } catch (error) {
     ElMessage.error('广告组创建失败!')
     console.error('请求失败:', error)

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

@@ -230,7 +230,7 @@
       <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">
+            <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>

+ 11 - 4
src/views/adManage/sb/campaigns/CreateCampaigns/component/VideoCommodity.vue

@@ -144,9 +144,9 @@
             </el-table-column>
           </el-table>
         </div>
-        <div style="display: flex; justify-content: space-around; padding-top: 5px">
+        <!-- <div style="display: flex; justify-content: space-around; padding-top: 5px">
           <el-button type="primary" plain :disabled="productSave" @click="submitProductForm">保存</el-button>
-        </div>
+        </div> -->
       </div>
     </div>
   </div>
@@ -157,7 +157,7 @@ import type { TabsPaneContext } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import { storeToRefs } from 'pinia'
 import type { Ref } from 'vue'
-import { inject, onMounted, ref, watch } from 'vue'
+import { inject, onMounted, ref, watch, provide } from 'vue'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { request } from '/@/utils/service'
 
@@ -181,7 +181,6 @@ const buttons = [{ type: 'primary', text: '添加' }] as const
 const productTabs = ref('first')
 const rightSelect = ref('latest')
 const respCampaignId = inject<Ref>('respCampaignId')
-const respCampaignName = inject<Ref>('respCampaignName')
 const respAdGroupId = inject<Ref>('respAdGroupId')
 
 function setTableData(asin = '', sku = '') {
@@ -379,6 +378,14 @@ const headerCellStyle = (args) => {
     }
   }
 }
+
+const emit = defineEmits(['update-added-data']);
+
+// 当数据发生变化时,触发事件
+watch(addedTableData, (newValue) => {
+  emit('update-added-data', newValue);
+}, {deep: true});
+
 onMounted(() => {
   setTableData()
 })

+ 332 - 101
src/views/adManage/sb/campaigns/CreateCampaigns/component/VideoCreativity2.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="customize-container">
-    <el-card body-style="padding: 20px 80px 0 80px;">
+    <el-card body-style="padding: 20px 80px 0 80px;" v-loading="loading">
       <div style="font-weight: 700; padding-bottom: 18px">
         <span style="color: #306cd7; font-size: 26px">|</span>
         <span style="font-size: 18px; padding-left: 5px">创意</span>
@@ -17,134 +17,175 @@
         <el-form-item label="广告名称" prop="name">
           <el-input v-model="ruleForm.name" style="width: 50%" />
         </el-form-item>
-        <el-form-item style="border: 1px solid #dddfe6;">
+        <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px">
           <div style="width: 50%; padding-left: 5px; border-right: 1px solid #dddfe6">
             <el-scrollbar height="700px">
-            <el-collapse v-model="activeNames" @change="handleChange">
-              <el-collapse-item name="1">
-                <template #title> <span style="color: #e47470; margin-right: 4px">*</span>视频 </template>
-                <div>
-                  <div style="display: flex; align-items: center; justify-content: space-between">
-                    <span style="color: #1e2128; font-size: 15px; font-weight: 450">选择视频</span>
-                    <el-button type="primary" text>查看视频批准提示</el-button>
-                  </div>
-                  <div style="color: #666666; margin-bottom: 10px">
-                    保持视频简短并紧扣主题。视频会自动播放,因此请确保前 2
-                    秒极具吸引力,并且不依靠声音来传递信息。如果您在视频中使用了文字,请确保文字清晰易辨。字幕或音频必须与将展示您广告的区域相匹配。
-                  </div>
-                  <div class="upload-button-group">
-                    <el-upload
-                      v-model:file-list="fileList"
-                      class="upload-demo"
-                      action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
-                      multiple
-                      :on-preview="handlePreview"
-                      :on-remove="handleRemove"
-                      :before-remove="beforeRemove"
-                      :limit="3"
-                      :on-exceed="handleExceed">
-                      <el-button type="primary">上传文件</el-button>
-
-                      <template #tip>
-                        <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
-                      </template>
-                    </el-upload>
-                    <el-button type="primary" :icon="Picture" style="margin-left: -120px" @click="handleSelect">从素材库中选择</el-button>
+              <el-collapse v-model="activeNames" @change="handleChange" style="border-top: none; border-bottom: none">
+                <el-collapse-item name="video" style="padding: 0 10px 0 5px">
+                  <template #title> <span style="color: #e47470; margin-right: 4px">*</span>视频 </template>
+                  <div>
+                    <div style="display: flex; align-items: center; justify-content: space-between">
+                      <span style="color: #1e2128; font-size: 15px; font-weight: 450">选择视频</span>
+                      <el-button type="primary" link>查看视频批准提示</el-button>
+                    </div>
+                    <div style="color: #666666; margin-bottom: 10px">
+                      保持视频简短并紧扣主题。视频会自动播放,因此请确保前 2
+                      秒极具吸引力,并且不依靠声音来传递信息。如果您在视频中使用了文字,请确保文字清晰易辨。字幕或音频必须与将展示您广告的区域相匹配。
+                    </div>
+                    <div class="upload-button-group">
+                      <el-upload
+                        v-model:file-list="fileList"
+                        class="upload-demo"
+                        action="#"
+                        accept=".mp4, .mov"
+                        :on-preview="handlePreview"
+                        :on-remove="handleRemove"
+                        :before-remove="beforeRemove"
+                        :limit="3"
+                        :on-exceed="handleExceed">
+                        <el-button type="primary">上传文件</el-button>
+
+                        <template #tip>
+                          <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
+                        </template>
+                      </el-upload>
+                      <el-button type="primary" :icon="Picture" style="margin-left: -120px" @click="handleSelect">从素材库中选择</el-button>
+                    </div>
                   </div>
+                </el-collapse-item>
+                <div style="display: flex; align-items: center; justify-content: space-between; padding: 0 10px 0 5px">
+                  <span style="color: #1e2128; font-size: 15px; font-weight: 450">视频文件要求:</span>
+                  <el-button type="primary" link>了解更多</el-button>
                 </div>
-              </el-collapse-item>
-              <div style="display: flex; align-items: center; justify-content: space-between">
-                <span style="color: #1e2128; font-size: 15px; font-weight: 450">视频文件要求:</span>
-                <el-button type="primary" text>了解更多</el-button>
-              </div>
-              <div>
-                <span style="color: #1e2128; font-size: 13px; font-weight: 450">视频规格</span>
-                <div class="introduce-item">1.纵横比:16:9</div>
-                <div class="introduce-item">2.尺寸:1280 x 720 像素、1920 x 1080 像素或 3840 x 2160 像素</div>
-                <div class="introduce-item">3.文件大小:500MB 或更小</div>
-                <div class="introduce-item">4.文件格式:MP4 或 MOV</div>
-                <div class="introduce-item">5.长度:6-45 秒</div>
-                <div class="introduce-item">6.帧率:23.976、23.98、24、25、29.97 或 29.98 fps</div>
-                <div class="introduce-item">7.比特率:1 Mbps 或更高</div>
-                <div class="introduce-item">8.编解码器:H.264 或 H.265</div>
-                <div class="introduce-item">9.配置文件:主配置文件或基线配置文件</div>
-                <div class="introduce-item">10.视频流:仅为 1</div>
-              </div>
-              <div>
-                <span style="color: #1e2128; font-size: 13px; font-weight: 450">音频规格</span>
-                <div class="introduce-item">1.语言:必须与广告投放区域匹配</div>
-                <div class="introduce-item">2.采样率:44.1 kHz 或更高</div>
-                <div class="introduce-item">3.编解码器:PCM、AAC 或 MP3</div>
-                <div class="introduce-item">4.比特率:96 kbps 或更高</div>
-                <div class="introduce-item">5.格式:立体声或单声道</div>
-                <div class="introduce-item">6.音频流:仅为 1</div>
-              </div>
-              <hr style="margin-top: 8px;">
-              <el-collapse-item name="2">
-                <template #title> <span style="color: #e47470; margin-right: 4px">*</span>商品 </template>
-                <div>Operation feedback: enable the users to clearly perceive their operations by style updates and interactive effects;</div>
-                <div>Visual feedback: reflect current state by updating or rearranging elements of the page.</div>
-              </el-collapse-item>
-            </el-collapse>
-          </el-scrollbar>
+                <div style="padding: 0 10px 0 5px">
+                  <span style="color: #1e2128; font-size: 13px; font-weight: 450">视频规格</span>
+                  <div class="introduce-item">1.纵横比:16:9</div>
+                  <div class="introduce-item">2.尺寸:1280 x 720 像素、1920 x 1080 像素或 3840 x 2160 像素</div>
+                  <div class="introduce-item">3.文件大小:500MB 或更小</div>
+                  <div class="introduce-item">4.文件格式:MP4 或 MOV</div>
+                  <div class="introduce-item">5.长度:6-45 秒</div>
+                  <div class="introduce-item">6.帧率:23.976、23.98、24、25、29.97 或 29.98 fps</div>
+                  <div class="introduce-item">7.比特率:1 Mbps 或更高</div>
+                  <div class="introduce-item">8.编解码器:H.264 或 H.265</div>
+                  <div class="introduce-item">9.配置文件:主配置文件或基线配置文件</div>
+                  <div class="introduce-item">10.视频流:仅为 1</div>
+                </div>
+                <div style="padding: 0 10px 0 5px">
+                  <span style="color: #1e2128; font-size: 13px; font-weight: 450">音频规格</span>
+                  <div class="introduce-item">1.语言:必须与广告投放区域匹配</div>
+                  <div class="introduce-item">2.采样率:44.1 kHz 或更高</div>
+                  <div class="introduce-item">3.编解码器:PCM、AAC 或 MP3</div>
+                  <div class="introduce-item">4.比特率:96 kbps 或更高</div>
+                  <div class="introduce-item">5.格式:立体声或单声道</div>
+                  <div class="introduce-item">6.音频流:仅为 1</div>
+                </div>
+                <hr style="color: #eceef4; margin: 8px 10px 0 5px" />
+                <el-collapse-item name="commodity" style="padding: 0 10px 0 5px">
+                  <template #title> <span style="color: #e47470; margin-right: 4px">*</span>商品 </template>
+                  <div v-for="item in addedTableDataForVc2" :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="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-card>
+                  </div>
+                </el-collapse-item>
+              </el-collapse>
+            </el-scrollbar>
           </div>
-        </el-form-item>
+          <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
+            >
+          </div>
+        </div>
       </el-form>
     </el-card>
+    <el-dialog v-model="centerDialogVisible" title="从素材库中选择" width="65%">
+      <el-input :prefix-icon="Search"></el-input>
+      <div class="grid-container">
+        <div
+          class="grid-item"
+          v-for="item in cards"
+          :key="item.id"
+          @click="selectCard(item)"
+          :class="{ selected: isSelected(item.id), hover: hoverId === item.id }"
+          @mouseover="hoverId = item.id"
+          @mouseleave="hoverId = null">
+          <el-card :body-style="{ padding: '0px' }">
+            <video class="image" :src="item.imageUrl" controls preload="none" @click.stop></video>
+            <div style="padding: 10px">
+              <span>
+                <el-tooltip placement="top" :content="item.title">
+                  {{ item.title }}
+                </el-tooltip>
+              </span>
+              <div class="bottom">
+                <div class="bottom-item">{{ item.size }}KB</div>
+                <div class="bottom-item">{{ item.width }} * {{ item.height }}</div>
+                <div class="bottom-item">背景视频</div>
+              </div>
+            </div>
+          </el-card>
+        </div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="centerDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleConfirmSelection">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
+import { reactive, ref, watch, inject, Ref } from 'vue'
 import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Picture} from '@element-plus/icons-vue'
+import { Plus, Picture, Search } from '@element-plus/icons-vue'
+import { getVideoAssets, videoDetailCreate } from '../api/index'
+import { storeToRefs } from 'pinia'
+import { useShopInfo } from '/@/stores/shopInfo'
+
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+
+const respAdGroupId = inject<Ref>('respAdGroupId')
+const addedTableDataForVc2 = inject<Ref>('addedTableDataForVc2')
 
 interface RuleForm {
   name: string
 }
-
 const ruleFormRef = ref<FormInstance>()
 const ruleForm = reactive<RuleForm>({
   name: '视频 广告 - 1/15/2024 17:51:10.236',
 })
-
 const rules = reactive<FormRules<RuleForm>>({
-  name: [
-    { required: true, message: 'Please input Activity name', trigger: 'blur' },
-    { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
-  ],
+  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!')
-    } else {
-      console.log('error submit!', fields)
-    }
-  })
-}
-
-const activeNames = ref(['1'])
+const activeNames = ref(['video'])
 const handleChange = (val: string[]) => {
-  console.log(val)
+  // 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.length = 0
+  // console.log(file, uploadFiles)
 }
 
 const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
@@ -162,9 +203,133 @@ const beforeRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => {
   )
 }
 
+// dialog以及选择card相关功能
+const selectedId = ref(null)
+const hoverId = ref(null)
+const selectedCards = ref([])
+const selectedImageUrl = ref('')
+const centerDialogVisible = ref(false)
+const cards = reactive([])
+let selectedAssetId = ''
+
+function resetSelection() {
+  selectedCards.value = []
+  selectedId.value = null
+}
+
+function bytesToKB(bytes) {
+  return (bytes / 1024).toFixed(2) // 保留两位小数
+}
+
+async function getAssetsData() {
+  const query = {
+    profile_id: profile.value.profile_id,
+    assetType: 'VIDEO',
+    specCheckApprovedPrograms: 'SPONSORED_BRANDS_VIDEO'
+  }
+  const response = await getVideoAssets(query)
+
+  cards.splice(0, cards.length)
+
+  response.data.forEach((asset) => {
+    cards.push({
+      id: asset.assetId,
+      title: asset.name,
+      imageUrl: asset.storageLocationUrls.defaultUrl,
+      width: asset.fileMetadata.resolutionWidth,
+      height: asset.fileMetadata.resolutionHeight,
+      size: bytesToKB(asset.fileMetadata.sizeInBytes),
+    })
+  })
+}
+
+function isSelected(id) {
+  return selectedCards.value.some((card) => card.id === id)
+}
+
+function selectCard(item) {
+  if (isSelected(item.id)) {
+    selectedCards.value = selectedCards.value.filter((card) => card.id !== item.id)
+  } else {
+    selectedCards.value.push({
+      id: item.id,
+      assetId: item.id,
+      title: item.title,
+      imageUrl: item.imageUrl,
+    })
+  }
+  selectedId.value = item.id
+}
+
+function handleConfirmSelection() {
+  if (selectedCards.value.length > 0) {
+    // 获取选中卡片的 assetId
+    selectedAssetId = selectedCards.value[0].assetId
+    const newFile = {
+      name: selectedCards.value[0].title, // 任何可以用作文件名的字符串
+      url: selectedImageUrl.value,
+      // 根据需要添加更多属性
+    }
+    fileList.value.push(newFile)
+
+    // 清空选中卡片
+    resetSelection()
+    centerDialogVisible.value = false
+  }
+}
+
 function handleSelect() {
-  // console.log()
+  centerDialogVisible.value = true
+  getAssetsData()
+}
+
+// 创建商品创意相关
+const loading = ref(false)
+async function createCommodity() {
+  try {
+    loading.value = true
+    const obj = {
+      profile_id: profile.value.profile_id,
+      casins: commodityCard.value[0].asin,
+      name: ruleForm.name,
+      state: 'ENABLED',
+      adGroupId: respAdGroupId.value,
+      consentToTranslate: false,
+      videoAssetIds: selectedAssetId,
+    }
+    const response = await videoDetailCreate(obj)
+    console.log('🚀 ~ response-->>', response)
+    if (response.data.creative_state == 'success') {
+      ElMessage({
+        message: '商品创意创建成功',
+        type: 'success',
+      })
+    } else {
+      ElMessage.error('商品创意创建失败!')
+    }
+  } catch (error) {
+    console.log('error:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+function clickSave() {
+  console.log(123, fileList.value)
+  createCommodity()
+  commodityCard.value.length = 0
 }
+
+// 已选择的商品
+const commodityCard = ref([])
+
+watch(
+  addedTableDataForVc2,
+  (newValue) => {
+    commodityCard.value = newValue
+  },
+  { deep: true }
+)
 </script>
 
 <style scoped>
@@ -179,4 +344,70 @@ function handleSelect() {
   font-size: 12px;
   color: #88909b;
 }
+.grid-container {
+  flex-wrap: wrap;
+  display: flex;
+  width: 100%;
+  justify-content: left;
+}
+.grid-item {
+  transition: outline, background-color 0.3s;
+  box-sizing: border-box;
+  border: 1px solid #ffffff00;
+  cursor: pointer;
+  width: calc(25% - 10px);
+  margin: 10px 5px;
+}
+.grid-item span {
+  display: block; /* 或者 inline-block */
+  white-space: nowrap; /* 保持文本在一行 */
+  overflow: hidden; /* 隐藏超出部分 */
+  text-overflow: ellipsis; /* 超出部分显示省略号 */
+  max-width: 100%; /* 限制最大宽度 */
+  font-weight: 600;
+  line-height: 22px;
+}
+.grid-item.hover,
+.grid-item.selected {
+  border: 1px solid #306cd8;
+  border-radius: 4px;
+}
+.grid-item.selected > :first-child {
+  background-color: #f5f7fe;
+}
+.image {
+  width: 100%;
+  height: 146.49px;
+  padding: 10px;
+}
+.image > :first-child {
+  border-radius: 10px;
+}
+.bottom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 5px;
+}
+.bottom-item {
+  background-color: #f5f7fe;
+  border-radius: 4px;
+  padding: 0 3px;
+}
+.img-box {
+  width: 60px;
+  height: 60px;
+  border: 1px solid rgb(194, 199, 207);
+  border-radius: 4px;
+}
+.double-line {
+  color: #1e2128;
+  font-weight: 500;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
 </style>

+ 10 - 5
src/views/adManage/sb/campaigns/CreateCampaigns/index.vue

@@ -6,12 +6,11 @@
       @update:adFormatRadio="handleAdFormatRadioChange"
       @update:arrivalsRadio="handleArrivalsRadioChange"
       @update:flagshipStoreShop="handleFlagshipStoreShopChange"
-      @update:pageOptions="handlePageOptionsChange"></AdFormat>
-    <VideoCreativity1
-      v-if="adFormatRadioValue === 'video' && arrivalsRadioValue === 'flagshipStore' && flagshipStoreShopValue === 'ZOSI'">
+      @update:pageOptions="handlePageOptionsChange"
+      @update:addedTableData="handleUpdateAddedData"></AdFormat>
+    <VideoCreativity1 v-if="adFormatRadioValue === 'video' && arrivalsRadioValue === 'flagshipStore' && flagshipStoreShopValue === 'ZOSI'">
     </VideoCreativity1>
-    <ProductSetCreativity1
-      v-if="adFormatRadioValue === 'productSet' && arrivalsRadioValue === 'flagshipStore' && flagshipStoreShopValue === 'ZOSI'">
+    <ProductSetCreativity1 v-if="adFormatRadioValue === 'productSet' && arrivalsRadioValue === 'flagshipStore' && flagshipStoreShopValue === 'ZOSI'">
     </ProductSetCreativity1>
     <VideoCreativity2 v-if="adFormatRadioValue === 'video' && arrivalsRadioValue === 'productDetailsPage'"></VideoCreativity2>
     <ProductSetCreativity2 v-if="adFormatRadioValue === 'productSet' && arrivalsRadioValue === 'newArrivals'"></ProductSetCreativity2>
@@ -37,11 +36,13 @@ const adFormatRadioValue = ref('')
 const arrivalsRadioValue = ref('')
 const flagshipStoreShopValue = ref('')
 const pageOptionsValue = ref('')
+const addedTableDataForVc2 = ref('')
 
 provide('respCampaignId', respCampaignId)
 provide('respCampaignName', respCampaignName)
 provide('respAdGroupId', respAdGroupId)
 provide('pageOptionsValue', pageOptionsValue)
+provide('addedTableDataForVc2', addedTableDataForVc2)
 
 const handleCampaignUpdate = (data) => {
   respCampaignId.value = data.id
@@ -62,6 +63,10 @@ const handleFlagshipStoreShopChange = (newValue) => {
 const handlePageOptionsChange = (newValue) => {
   pageOptionsValue.value = newValue
 }
+
+function handleUpdateAddedData(data) {
+  addedTableDataForVc2.value = data
+}
 </script>
 
 <style scoped>

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

@@ -0,0 +1,96 @@
+import { request } from '/@/utils/service'
+
+
+export function getAdMixSelect() {
+  return request({
+      url: '/api/ad_manage/portfolios/select_list',
+      method: 'GET',
+  })
+}
+
+export function postCampaignsData(filteredRequestData) {
+  return request({
+      url: '/api/ad_manage/sbcampaigns/create/',
+      method: 'post',
+      data: filteredRequestData,
+  })
+}
+
+export function postGroupData(filteredRequestData) {
+  return request({
+      url: '/api/ad_manage/sbgroups/create/',
+      method: 'post',
+      data: filteredRequestData,
+  })
+}
+
+export function postNegativeWordData(filteredRequestData) {
+  return request({
+      url: '/api/ad_manage/sptargets/add/negative/keywords/',
+      method: 'post',
+      data: filteredRequestData,
+  })
+}
+
+export function getAssets(query) {
+  return request({
+      url: '/api/ad_manage/sb/assets/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getLifeStyleAssets(query) {
+  return request({
+      url: '/api/ad_manage/sb/assets/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getBrands(query) {
+  return request({
+    url: '/api/ad_manage/sb/getbrands/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getStoreurl(query) {
+  return request({
+    url: '/api/ad_manage/sb/storeurl/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getPageAsins(query) {
+  return request({
+    url: '/api/ad_manage/sb/getpageasins/',
+      method: 'get',
+      params: query
+  })
+}
+export function getCommodityCard(query) {
+  return request({
+    url: '/api/sellers/listings/all/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function getVideoAssets(query) {
+  return request({
+      url: '/api/ad_manage/sb/assets/',
+      method: 'get',
+      params: query
+  })
+}
+
+export function videoDetailCreate(obj) {
+  return request({
+      url: '/api/ad_manage/sbads/video/create/',
+      method: 'post',
+      data: obj,
+  })
+}

+ 210 - 0
src/views/adManage/sd/campaigns/CreateCampaigns/component/AdCampaign.vue

@@ -0,0 +1,210 @@
+<template>
+  <div class="customize-container">
+    <el-card body-style="padding: 20px 80px 0 80px;" v-loading="campaignLoading">
+      <div style="font-weight: 700; padding-bottom: 18px">
+        <span style="color: #306cd7; font-size: 26px">|</span>
+        <span style="font-size: 18px; padding-left: 5px">广告活动</span>
+      </div>
+      <el-form
+        ref="campaignRuleFormRef"
+        :model="campaignRuleForm"
+        :rules="campaignRules"
+        label-position="top"
+        label-width="120px"
+        class="demo-ruleForm"
+        size="default"
+        status-icon>
+        <div class="flex-between">
+          <el-form-item label="广告活动名称" prop="campaignName" style="width: 48%">
+            <el-input v-model="campaignRuleForm.campaignName" />
+          </el-form-item>
+          <el-form-item label="广告组合" prop="adMix" style="width: 48%">
+            <el-select v-model="campaignRuleForm.adMix" placeholder="请选择" style="width: 100%">
+              <el-option v-for="item in adMixOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </div>
+        <div class="flex-between">
+          <div class="flex-between" style="width: 48%">
+            <el-form-item label="开始时间" prop="startDate" style="width: 49%">
+              <el-date-picker
+                v-model="campaignRuleForm.startDate"
+                type="date"
+                label="Pick a date"
+                placeholder="开始时间"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="结束时间" prop="endDate" style="width: 49%">
+              <el-date-picker
+                v-model="campaignRuleForm.endDate"
+                type="date"
+                label="Pick a date"
+                placeholder="开始时间"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 100%" />
+            </el-form-item>
+          </div>
+          <div class="flex-between" style="width: 48%">
+            <el-form-item label="预算" required prop="budget" style="width: 100%">
+              <el-input v-model="campaignRuleForm.budget" minlength="1" maxlength="7" placeholder="请输入" style="width: 100%">
+                <template #prepend>$</template>
+              </el-input>
+            </el-form-item>
+          </div>
+        </div>
+        <div>
+          <el-radio-group v-model="radio">
+            <el-radio label="1">受众</el-radio>
+            <el-radio label="2">内容相关投放</el-radio>
+          </el-radio-group>
+        </div>
+        <el-form-item style="margin: 20px 0 -10px 48%">
+          <el-button type="primary" plain @click="submitCampaignForm(campaignRuleFormRef)">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</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 { useShopInfo } from '/@/stores/shopInfo'
+import { postCampaignsData, getAdMixSelect } from '../api/index'
+
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+
+const campaignRuleFormRef = ref<FormInstance>()
+interface campaignRuleForm {
+  campaignName: string
+  adMix: string
+  startDate: string
+  endDate: string
+  budget: string
+}
+const campaignRuleForm = reactive<campaignRuleForm>({
+  campaignName: 'AiTestW01',
+  adMix: '',
+  startDate: '',
+  endDate: '',
+  budget: '1',
+})
+const campaignRules = reactive<FormRules<campaignRuleForm>>({
+  campaignName: [{ required: true, message: '请输入广告活动', trigger: 'blur' }],
+  startDate: [{ type: 'date', required: true, message: '请选择时间', trigger: 'blur' }],
+  budget: [
+    { required: true, message: '请输入预算', trigger: 'blur' },
+    { pattern: /^(?:[1-9]\d{0,5}|1000000)(?:\.\d{1,2})?$/, message: '预算必须是1到1000000之间的数字,小数点后最多两位', trigger: 'blur' },
+  ],
+})
+const submitCampaignForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      createCampaigns()
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const radio = ref('1')
+
+// 广告组合相关
+const adMixOptions = ref([])
+async function buildAdMix() {
+  try {
+    const response = await getAdMixSelect()
+    adMixOptions.value = response.data.map((option) => {
+      return {
+        value: option.portfolioId,
+        label: option.name,
+      }
+    })
+  } catch (error) {
+    console.error('请求失败:', error)
+  }
+}
+
+// 创建广告活动相关功能
+const campaignLoading = ref(false)
+const respCampaignId = ref('')
+const respCampaignName = ref('')
+async function createCampaigns() {
+  campaignLoading.value = true
+
+  // 构建基础请求体
+  const campaignData = {
+    profile_id: profile.value.profile_id,
+    budget: campaignRuleForm.budget,
+    name: campaignRuleForm.campaignName,
+    bidOptimizationStrategy: '',
+    startDate: campaignRuleForm.startDate,
+    endDate: campaignRuleForm.endDate,
+    smartDefault: 'MANUAL',
+    costType: 'CPC',
+    goal: 'PAGE_VISIT',
+    state: 'ENABLED',
+  }
+
+  try {
+    const response = await postCampaignsData(campaignData)
+    respCampaignId.value = response.data.campaignId
+    respCampaignName.value = response.data.campaignName
+
+    if (response.data.campaignId) {
+      ElMessage({ message: '广告活动创建成功', type: 'success' })
+    } else {
+      ElMessage.error('广告活动创建失败!')
+    }
+  } catch (error) {
+    console.error('请求失败:', error)
+  } finally {
+    campaignLoading.value = false
+  }
+}
+
+// 提供数据给父组件
+const emit = defineEmits(['update-campaign'])
+
+watch([respCampaignId, respCampaignName], () => {
+  if (respCampaignId.value && respCampaignName.value) {
+    emit('update-campaign', {
+      id: respCampaignId.value,
+      name: respCampaignName.value,
+    })
+  }
+})
+
+onMounted(() => {
+  buildAdMix()
+})
+</script>
+
+<style scoped>
+.flex-between {
+  display: flex;
+  justify-content: space-between;
+}
+.left {
+  margin-right: 12px;
+  width: 60%;
+}
+.title {
+  font-size: 14px;
+  line-height: 20px;
+  color: #1d2129;
+}
+.tip {
+  font-size: 14px;
+  line-height: 20px;
+  color: #4e5969;
+}
+</style>

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

@@ -1,9 +1,16 @@
 <template>
-  <p>新建广告页面</p>
+  <div class="page-container">
+    <AdCampaign></AdCampaign>
+  </div>
 </template>
 
 <script lang="ts" setup>
+import AdCampaign from './component/AdCampaign.vue'
 </script>
 
 <style scoped>
+.page-container {
+  padding: 12px;
+  background-color: #fafafa;
+}
 </style>

+ 14 - 6
src/views/adManage/sd/campaigns/crud.tsx

@@ -4,6 +4,8 @@ import {inject} from 'vue'
 import {SdBaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
 import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
 import XEUtils from 'xe-utils'
+import { useRouter } from 'vue-router'
+
 
 export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
   const pageRequest = async (query: UserPageQuery) => {
@@ -22,6 +24,8 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
     return await api.AddObj(form)
   }
 
+  const router = useRouter()
+
   //权限判定
   const hasPermissions = inject('$hasPermissions')
 
@@ -48,19 +52,23 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         show: true,
         buttons: {
           add: {
-            show: false
+            show: false,
           },
           create: {
             text: '新建广告活动',
-            // type: 'primary',
+            type: 'primary',
+            show: true,
             color: "#626aef",
             plain: true,
-            show: true,
             click() {
-
-            }
+              router.push({
+                name: 'SdCreateCampaigns',
+                query: { campaignId: 123, tagsViewName: '新建广告活动' },
+              })
+            
+            },
           },
-        }
+        },
       },
       search: {
         show: false