Browse Source

落地页

xiuli.gao 1 year ago
parent
commit
cddf7a20a9

+ 42 - 43
src/components/businessMoudle/batchGdt/configArea/basicInfo/outer.scss

@@ -8,56 +8,55 @@
8 8
   .tabelCon {
9 9
     flex: 1;
10 10
   }
11
+}
12
+.ids {
13
+  display: flex;
14
+  flex-basis: 140px;
15
+  flex-direction: column;
16
+  flex-grow: 0;
17
+  flex-shrink: 0;
18
+  max-width: 175px;
19
+  height: 100%;
20
+  border-right: 1px solid #ebeef5;
21
+}
11 22
 
12
-  .ids {
13
-    display: flex;
14
-    flex-basis: 140px;
15
-    flex-direction: column;
16
-    flex-grow: 0;
17
-    flex-shrink: 0;
18
-    max-width: 175px;
19
-    height: 100%;
20
-    border-right: 1px solid #ebeef5;
21
-  }
22
-
23
-  .title {
24
-    padding: 6px 30px 6px 8px;
25
-    font-weight: 700;
26
-    line-height: 23px;
27
-    border-bottom: 1px solid #ebeef5;
28
-    color: #606266;
29
-    height: 36px;
30
-  }
23
+.title {
24
+  padding: 6px 30px 6px 8px;
25
+  font-weight: 700;
26
+  line-height: 23px;
27
+  border-bottom: 1px solid #ebeef5;
28
+  color: #606266;
29
+  height: 36px;
30
+}
31 31
 
32
-  .refresh {
33
-    font-size: 12px;
34
-    font-weight: 400;
35
-    color: #197afb;
36
-    cursor: pointer;
37
-    margin-left: 20px;
38
-  }
32
+.refresh {
33
+  font-size: 12px;
34
+  font-weight: 400;
35
+  color: #197afb;
36
+  cursor: pointer;
37
+  margin-left: 20px;
38
+}
39 39
 
40
-  .accItem {
41
-    display: flex;
42
-    flex-direction: row;
43
-    flex-wrap: nowrap;
44
-    align-items: center;
45
-    height: 60px;
46
-    padding: 6px 8px;
47
-    color: #606266;
48
-    cursor: pointer;
49
-    border-bottom: 1px solid #ebeef5;
40
+.accItem {
41
+  display: flex;
42
+  flex-direction: row;
43
+  flex-wrap: nowrap;
44
+  align-items: center;
45
+  height: 60px;
46
+  padding: 6px 8px;
47
+  color: #606266;
48
+  cursor: pointer;
49
+  border-bottom: 1px solid #ebeef5;
50 50
 
51
-    &.active {
52
-      color: #197afb;
53
-      background-color: #fafafa;
51
+  &.active {
52
+    color: #197afb;
53
+    background-color: #fafafa;
54 54
 
55
-    }
56
-  }
57
-  .conInfo {
58
-    padding: 20px;
59 55
   }
60 56
 }
57
+.conInfo {
58
+  padding: 20px;
59
+}
61 60
 .warn-content{
62 61
   color: #f1902b;
63 62
   font-size: 12px;

+ 53 - 23
src/components/businessMoudle/batchGdt/configArea/index.vue

@@ -40,7 +40,7 @@
40 40
                   <Copywriter ref="CopywriterRef" @writerCallBack="writerCallBack"></Copywriter>
41 41
                 </template>
42 42
                 <template v-if="sub.name == '落地页'">
43
-                  <LandPage ref="LandPageRef"></LandPage>
43
+                  <LandPage ref="LandPageRef" :accIdsList="pageInfo.accIdsList" :page_type="originalityBasicInfoData?.receiveForm?.landingPageExample?.page_type"></LandPage>
44 44
                 </template>
45 45
                 <div class="noData" v-if="!sub.haveContent">暂无数据</div>
46 46
                 <!-- 定向包展示 -->
@@ -299,14 +299,7 @@ const DirectPacketExhibitionRef = ref<{get_echoAcId:()=>void,showDirectPacket:()
299 299
 
300 300
 // 打开添加 / 编辑
301 301
 const openDialogEvent = (sub:any) => {
302
-  if(pageInfo.accIdsList && pageInfo.accIdsList.length<=0){
303
-    ElMessage.error('请选择媒体账户!')
304
-    return
305
-  }
306
-  if(!pageInfo.targetValue || pageInfo.targetValue == ''){
307
-    ElMessage.error('请选择推广目标!')
308
-    return
309
-  }
302
+  if( !judgeEvent( sub.id ) ) return; // 判断是否完成前一模块
310 303
   if(sub.name == '定向包'){
311 304
     nextTick(()=>{
312 305
       DirectPacketExhibitionRef.value![0].showDirectPacket()
@@ -333,26 +326,63 @@ const openDialogEvent = (sub:any) => {
333 326
     }
334 327
   }
335 328
   if(sub.name == '创意文案'){
336
-    console.log(pageInfo)
337
-    if(!basicInfoData.fillBack || basicInfoData.fillBack == '' || JSON.stringify(basicInfoData.fillBack) == '{}'){
338
-      ElMessage.warning('请完善广告基本信息!')
339
-      return
340
-    }
341
-    if(!pageInfo.directObj || pageInfo.directObj == '' || JSON.stringify(pageInfo.directObj) == '{}'){
342
-      ElMessage.warning('请选择定向包!')
343
-      return
344
-    }
345
-    if(!originalityBasicInfoData.params || originalityBasicInfoData.params == '' || originalityBasicInfoData.params.length <= 0){
346
-      ElMessage.warning('完善创意基本信息!')
347
-      return;
348
-    }
349 329
     CopywriterRef.value![0].initFun(true)
350 330
   }
351 331
   if(sub.name == '落地页'){
352 332
     LandPageRef.value![0].initFun(true)
353 333
   }
354 334
 }
355
-
335
+/**每个模块判断是否可以点击添加 id:模块ID*/
336
+const judgeEvent = (id) => {
337
+  if(pageInfo.accIdsList && pageInfo.accIdsList.length<=0){
338
+    ElMessage.error('请选择媒体账户!')
339
+    return false
340
+  }
341
+  if(!pageInfo.targetValue || pageInfo.targetValue == ''){
342
+    ElMessage.error('请选择推广目标!')
343
+    return false
344
+  }
345
+  if(id == 2) return true;//广告基本信息
346
+  if(!basicInfoData.fillBack || basicInfoData.fillBack == '' || JSON.stringify(basicInfoData.fillBack) == '{}'){
347
+    ElMessage.warning('请完善广告基本信息!')
348
+    return false
349
+  }
350
+  if(id == 3) return true;//定向包
351
+  if(id == 5) return true;//创意基本信息
352
+  if(id != 6 && (!pageInfo.directObj || pageInfo.directObj == '' || JSON.stringify(pageInfo.directObj) == '{}')){
353
+    ElMessage.warning('请选择定向包!')
354
+    return false
355
+  }
356
+  if(!originalityBasicInfoData.params || originalityBasicInfoData.params == '' || originalityBasicInfoData.params.length <= 0){
357
+    ElMessage.warning('完善创意基本信息!')
358
+    return false
359
+  }
360
+  if(id == 6) return true;//创意素材 不用判断定向包是否填写
361
+  //  --------   创意素材是否完善判断 -------
362
+  if(id == 7) {//创意文案
363
+    if( !originalityBasicInfoData.apiResult?.adTemplateItem?.adcreative_elements?.description ){
364
+      ElMessage.warning('此创意形式下不可使用创意文案!')
365
+      return false
366
+    } else{
367
+      return true
368
+    }
369
+  };
370
+  // console.log(originalityBasicInfoData.receiveForm.landingPageExample.page_type)
371
+  if(id == 8) {
372
+    if(!originalityBasicInfoData?.receiveForm?.landingPageExample?.page_type){
373
+      ElMessage.warning('此创意形式下不可使用落地页!')
374
+      return false;//落地页
375
+    } else {
376
+      return true
377
+    }
378
+  }
379
+  if(id == -1){ // 提交预览时,判断是否全部完善
380
+  //  --------   创意文案是否完善判断 -------
381
+  //  --------   落地页是否完善判断 -------
382
+  // ----- 外部配置是否完善 ----- 
383
+  }
384
+  return true
385
+}
356 386
 
357 387
 //更新数值,判断是否有内容
358 388
 const updateHaveContent = (id:number,chooseNum?:number,minusFlag?:boolean) => {

+ 100 - 0
src/components/businessMoudle/batchGdt/configArea/landPage/accMinprogram.vue

@@ -0,0 +1,100 @@
1
+<template>
2
+<div class="tabelListBox">
3
+    <div class="ids">
4
+      <div class="accItem" v-for="(item, idx) in accIdsList" :key="item.id" :class="item.id == accId ? 'active' : ''"
5
+        @click="accItemEvent(item)">
6
+        {{ item.name || '-' }}
7
+        <el-icon :size="14" :color="item.id == accId ? '#197afb' : '#606266'" class="lMarauto">
8
+          <CircleCheck v-if="true" />
9
+          <i-ep-ArrowRight v-else />
10
+        </el-icon>
11
+      </div>
12
+    </div>
13
+    <div class="tabelCon">
14
+      <LandPageList ref="LandPageListRef" :page_type="page_type" :select_count="select_count" @callBack="handleSelectionChange"></LandPageList>
15
+    </div>
16
+  </div>
17
+</template>
18
+<script setup lang="ts">
19
+import LandPageList from './landPageList.vue';
20
+import { nextTick, onMounted, reactive, ref } from 'vue';
21
+const props = defineProps({
22
+  accIdsList: {
23
+    type: Array<{ id: string, name: string }>,
24
+    default: () => []
25
+  },
26
+  page_type: {
27
+    type: String,
28
+    default: ''
29
+  },
30
+  select_count: {
31
+    type: Number,
32
+    default: -1
33
+  }
34
+})
35
+const emit = defineEmits<{
36
+  (event: "callBack", val?: any): void;
37
+}>();
38
+const accId = ref('')
39
+const LandPageListRef = ref()
40
+const callDataInfo = reactive({})
41
+const accCountInfo = reactive({})
42
+onMounted(()=>{
43
+  nextTick(()=>{
44
+    props.accIdsList.forEach((item)=>{
45
+      callDataInfo[item.id] = []
46
+      accCountInfo[item.id] = {
47
+        count: -1
48
+      }
49
+    })
50
+    if(props.select_count != -1) {
51
+      //分配条数 accCountInfo
52
+    }
53
+    accItemEvent({id: props.accIdsList[0]?.id})
54
+  })
55
+})
56
+/**点击媒体账户 */
57
+const accItemEvent = (item, type?, cb?:Function) => {
58
+  accId.value = item?.id;
59
+  LandPageListRef.value!.initFun(accId.value)
60
+}
61
+const handleSelectionChange = (val) => {
62
+  callDataInfo[accId.value] = val
63
+  let msg = '';
64
+  for(let i in callDataInfo){
65
+    if(callDataInfo[i].length == 0){
66
+      msg = `账户${i},请选择落地页!`
67
+    }
68
+  }
69
+  if(val.length < accCountInfo[accId.value]?.count && accCountInfo[accId.value]?.count != -1){
70
+    msg = `当前应选择${accCountInfo[accId.value]?.count}个落地页,您的选择数量为:${val.length}`
71
+  }
72
+  emit('callBack',{
73
+    data: callDataInfo,
74
+    msg: msg
75
+  })
76
+}
77
+</script>
78
+<style scoped lang="scss">
79
+@import "@/components/businessMoudle/batchGdt/configArea/basicInfo/outer.scss";
80
+.tabelListBox{
81
+  margin-top: 15px;
82
+  height: 56vh;
83
+  .tabelCon{
84
+    overflow: auto;
85
+    height: 100%;
86
+  }
87
+}
88
+.landPageminiprogram{
89
+  :deep(.el-table__cell){
90
+    font-size: 12px !important;
91
+  }
92
+  :deep(.filter-wrap){
93
+    background-color: #f8f8f8;
94
+    padding: 10px ;
95
+  }
96
+  :deep(.page-wrap){
97
+    margin-top: 0;
98
+  }
99
+}
100
+</style>

+ 12 - 3
src/components/businessMoudle/batchGdt/configArea/landPage/index.vue

@@ -17,25 +17,34 @@
17 17
     <p class="add-sec-title">文案:</p>
18 18
     <div class="add-sec-span" v-for="item in writer_list">{{ item }}</div>
19 19
   </div>
20
-  <LandPageDialog ref="LandPageDialogRef" @close="closeWriterDialog"></LandPageDialog>
20
+  <LandPageDialog ref="LandPageDialogRef" @close="closeWriterDialog" :accIdsList="accIdsList" :page_type="page_type"></LandPageDialog>
21 21
 </template>
22 22
 <script setup lang="ts">
23
-import { nextTick, ref } from 'vue';
23
+import { nextTick, reactive, ref } from 'vue';
24 24
 import LandPageDialog from './landPageDialog.vue'
25
+
25 26
 const selectOptions = [
26 27
   { label: '自动分配', value: 1 },
27 28
   { label: '程序化测试', value: 2 },
28 29
 ]
29
-const writer_list = ref<any[]>([])
30 30
 const props = defineProps({
31 31
   showFlag: {
32 32
     type: Boolean,
33 33
     default: false
34
+  },
35
+  accIdsList: {
36
+    type: Array<{ id: string, name: string }>,
37
+    default: () => []
38
+  },
39
+  page_type: {
40
+    type: String,
41
+    default: ''
34 42
   }
35 43
 })
36 44
 const emit = defineEmits<{
37 45
   (event: "writerCallBack", val?: any): void;
38 46
 }>();
47
+const writer_list = ref<any[]>([])
39 48
 const LandPageDialogRef = ref()
40 49
 const selectValue = ref(1)
41 50
 const tooltip = `自动匹配:将落地页分配至账户/计划/广告创意中。<br/>程序化测试:落地页与定向、创意、文案进行叉乘测试。`

+ 140 - 60
src/components/businessMoudle/batchGdt/configArea/landPage/landPageDialog.vue

@@ -11,9 +11,9 @@
11 11
           </el-radio-group>
12 12
         </div>
13 13
       </div>
14
-      <div class="flex marT15">
14
+      <div class="flex marT15" v-if="allocation == 1">
15 15
         <label class="form-block-item-title">落地页分配规则</label>
16
-        <el-radio-group v-model="loadPageRule" size="default" @change="">
16
+        <el-radio-group v-model="loadPageRule" size="default" @change="loadPageRuleChange">
17 17
           <el-radio-button :label="item.id" v-for="item in loadPageRuleList">{{ item.name }}</el-radio-button>
18 18
         </el-radio-group>
19 19
         <el-tooltip placement="right" effect="light">
@@ -21,12 +21,23 @@
21 21
           <span><el-icon class="questionFilled lMar5"><QuestionFilled /></el-icon></span>
22 22
         </el-tooltip>
23 23
         <div class="num_pre">
24
-          <span>账户数:</span><span class="num">3,</span>
25
-          <span>计划数:</span><span class="num">3,</span>
26
-          <span>广告数:</span><span class="num">3;</span>
24
+          <span>账户数:</span><span class="num">{{ count_info.acc_count }},</span>
25
+          <span>计划数:</span><span class="num">{{ count_info.plan_count || 0 }},</span>
26
+          <span>广告数:</span><span class="num">{{ count_info.ad_count || 0 }};</span>
27 27
         </div>
28 28
       </div>
29
-
29
+      <div class="flex marT15" v-if="allocation == 2">
30
+        <label class="form-block-item-title">测试规则</label>
31
+        <el-radio-group v-model="testRuleInfo" size="default">
32
+          <el-radio-button :label="item.id" v-for="item in testRules.list">{{ item.name }}</el-radio-button>
33
+        </el-radio-group>
34
+        <el-tooltip placement="right" effect="light">
35
+          <template #content><span v-html="testRules.msg"></span></template>
36
+          <span><el-icon class="questionFilled lMar5"><QuestionFilled /></el-icon></span>
37
+        </el-tooltip>
38
+      </div>
39
+      <LandPageList :page_type="page_type" :select_count="select_count" @callBack="handleSelectionChange" v-if="multiCopyTesting == 0" style="margin-top:20px"></LandPageList>
40
+      <AccMinprogram v-if="multiCopyTesting == 1" :page_type="page_type" :select_count="select_count" :accIdsList="accIdsList" @callBack="AccMinprogramChange"></AccMinprogram>
30 41
     </div>
31 42
     <template #footer>
32 43
       <div class="dialog-footer" style="text-align: right;padding:10px">
@@ -37,93 +48,162 @@
37 48
   </el-dialog>
38 49
 </template>
39 50
 <script setup lang="ts">
40
-import { onBeforeMount, reactive, ref } from 'vue';
41
-import {emojiList} from "@/common/emoji";
42
-import TextLibrary from '@/components/businessMoudle/textLibrary/index.vue'
43
-import WriterHelper from './writerHelper.vue'
51
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
44 52
 import { ElMessage } from 'element-plus';
53
+import LandPageList from './landPageList.vue';
54
+import AccMinprogram from './accMinprogram.vue'
45 55
 const props = defineProps({
46 56
   accIdsList: {
47 57
     type: Array<{ id: string, name: string }>,
48 58
     default: () => []
59
+  },
60
+  page_type: {
61
+    type: String,
62
+    default: ''
49 63
   }
50 64
 })
51 65
 const emit = defineEmits<{
52 66
   (event: "close", val?: any): void;
53 67
 }>();
54 68
 
55
-const loadPageRuleList = reactive([
56
-  { id: 0, name: '全部相同' },
57
-  { id: 1, name: '按账户分配' },
58
-  { id: 2, name: '分配到计划' },
59
-  { id: 3, name: '分配到广告' },
60
-])
69
+const count_info = reactive({
70
+  acc_count: 0,
71
+  plan_count: 3,
72
+  ad_count: 10
73
+})
74
+const testRuleInfo = ref(1)
75
+const testRules = {
76
+  msg: `全量测试:各个账户内资产与所有落地页叉乘;<br/>分账户测试:先将落地页均分至各个账户后,与每个账户下的资产分别叉乘;`,
77
+  list: [
78
+    {id:1,name:'全量测试'},
79
+    {id:2,name:'分账户测试'},
80
+  ]
81
+}
82
+
83
+const loadPageRuleList = ref<any[]>([])
61 84
 const loadPageRule = ref(0)
62
-const ruleTooltip = `全部相同:全部广告使用同一个落地页;<br/>按账户分配:一个账户内广告使用同一个落地页;<br/>分配到计划:一个计划内广告使用同一个落地页;<br/>分配到广告:一个广告一个落地页;`
85
+const ruleTooltip = ref<string>()
63 86
 const visible = ref(false)
64 87
 const loading = ref(false)
65
-const text_textarea = ref()
66
-const text_list = ref<any[]>([])
67 88
 const multiCopyTesting = ref(0)
68
-const WriterHelperRef = ref()
69 89
 const allocation = ref(1) // 创意文案分配方式 1自动分配 2程序化测试
70
-const tooltip = `您可以选择某一种文案,进行多条文案测试;<br/>测试文案为变量,将会出现在最下方,以列表形式多选;<br/>非测试文案为常量,会保持在上方;`
90
+const multipleSelection = ref<any[]>([])
91
+const select_count = ref(-1) //限制选中的条数
92
+const accMinprogramInfo = ref();
93
+
94
+/**自动扩量监听 */
95
+watch(
96
+  () => multiCopyTesting.value
97
+, (newValue, oldValue) => {
98
+  if(newValue == 0) {
99
+    loadPageRuleList.value = [
100
+      { id: 0, name: '全部相同' },
101
+      { id: 1, name: '按账户分配' },
102
+      { id: 2, name: '分配到计划' },
103
+      { id: 3, name: '分配到广告' },
104
+    ]
105
+    ruleTooltip.value = `全部相同:全部广告使用同一个落地页;<br/>按账户分配:一个账户内广告使用同一个落地页;<br/>分配到计划:一个计划内广告使用同一个落地页;<br/>分配到广告:一个广告一个落地页;`
106
+  }
107
+  if(newValue == 1){
108
+    loadPageRuleList.value = [
109
+      { id: 0, name: '全部相同' },
110
+      { id: 2, name: '分配到计划' },
111
+      { id: 3, name: '分配到广告' },
112
+    ]
113
+    ruleTooltip.value = `全部相同:全部广告使用同一个落地页;<br/>分配到计划:一个计划内广告使用同一个落地页;<br/>分配到广告:一个广告一个落地页;`
114
+  }
115
+},{ immediate: true})
71 116
 
72 117
 onBeforeMount(() => {
73
-  init()
74 118
 })
75
-const init = () => {
76
-}
77
-const appendText = (val:any) => {
78
-  var elInput:any = document.getElementById('emojiInput'); //根据id选择器选中对象
79
-  var startPos:any = elInput.selectionStart;// input 第0个字符到选中的字符
80
-  var endPos:any = elInput.selectionEnd;// 选中的字符到最后的字符
81
-  if (startPos === undefined || endPos === undefined) return
82
-  var txt = elInput.value;
83
-  // 将表情添加到选中的光标位置
84
-  var result = txt.substring(0, startPos) + val + txt.substring(endPos)
85
-  elInput.value = result;// 赋值给input的value
86
-  // 重新定义光标位置
87
-  elInput.focus();
88
-  elInput.selectionStart = startPos + val.length;
89
-  elInput.selectionEnd = endPos + val.length;
90
-  text_textarea.value  = result// 赋值给表单中的的字段
91
-}
92
-/**文案助手确定回调 */
93
-const writerHelperClose = (obj) =>{
94
-  if(obj && Array.isArray(obj) && obj.length > 0) {
95
-    text_textarea.value = obj[0].content
119
+const loadPageRuleChange = () => {
120
+  if(allocation.value == 2) {
121
+    select_count.value = -1
122
+    return
123
+  }
124
+  if( loadPageRule.value == 0 ) {
125
+    select_count.value = 1
126
+  }
127
+  if( loadPageRule.value == 1 ){
128
+    select_count.value = count_info.acc_count
129
+  }
130
+  if(loadPageRule.value == 2){
131
+    select_count.value = count_info.plan_count
132
+  }
133
+  if(loadPageRule.value == 3){
134
+    select_count.value = count_info.ad_count
96 135
   }
97 136
 }
98
-
99 137
 const handleClose = () => {
100 138
   visible.value = false
101 139
 }
102
-const selectEvent = (obj) => {
103
-  text_list.value = obj.map((v)=>{return v.content})
104
-}
105 140
 /**点击确定 */
106 141
 const submitEvent = () => {
107
-  if(multiCopyTesting.value == 0) {
108
-   if( text_textarea.value == '' || !text_textarea.value ){
109
-    ElMessage.warning('请输入文案!')
110
-    return
111
-   }else{
112
-    text_list.value = [text_textarea.value]
113
-   }
114
-  }
115
-  if(multiCopyTesting.value == 1 && text_list.value?.length <= 0){
116
-    ElMessage.warning('请选择测试的文案内容!')
117
-    return
142
+  let emitData = {}
143
+  if(multiCopyTesting.value == 1){//开启
144
+    if(allocation.value == 2) {//程序化叉乘
145
+      if(testRuleInfo.value == 1 ){//
146
+
147
+      }
148
+    }
149
+  }else{//不开启
150
+    if(allocation.value == 2) {//程序化叉乘
151
+      if(testRuleInfo.value == 1 ){//全量测试
152
+        if(multipleSelection.value.length == 0){
153
+          ElMessage.warning('请选择落地页!')
154
+          return
155
+        }else{
156
+          props.accIdsList.forEach((item)=>{
157
+            emitData[item.id] = multipleSelection.value
158
+          })
159
+        }
160
+      }
161
+      if(testRuleInfo.value == 2) {//分账户测试
162
+        props.accIdsList.forEach((item)=>{
163
+          emitData[item.id] = []
164
+        })
165
+        // multipleSelection.value -------再这里了
166
+      }
167
+    }
118 168
   }
119
-  emit('close', text_list.value)
120
-  visible.value = false
169
+    emit('close', emitData)
170
+  // if(multiCopyTesting.value == 1){//开启
171
+  //   if(allocation.value == 2) {//程序化叉乘
172
+  //     if(testRuleInfo.value == 1 ){//
173
+
174
+  //     }
175
+  //   }
176
+  //   if(accMinprogramInfo.value.msg != ''){
177
+  //     ElMessage.warning(accMinprogramInfo.value.msg)
178
+  //     return
179
+  //   }else{
180
+  //     visible.value = false
181
+  //     emit('close',accMinprogramInfo.value.data)
182
+  //   }
183
+  // }else{
184
+  //   if(multipleSelection.value.length < select_count.value){
185
+  //     ElMessage.warning(`当前应选择${select_count.value}个落地页,您的选择数量为:${multipleSelection.value.length}`)
186
+  //     return;
187
+  //   }
188
+  //   visible.value = false
189
+  //   emit('close', multipleSelection.value)
190
+  // } 
191
+}
192
+const handleSelectionChange = (val) => {
193
+  multipleSelection.value = val
194
+}
195
+const AccMinprogramChange = (obj) => {
196
+  accMinprogramInfo.value = obj
121 197
 }
122 198
 
123 199
 /**初始化 */
124 200
 const initFun = (flag, select) => {
125 201
   visible.value = flag
126 202
   allocation.value = select
203
+  nextTick(()=>{
204
+    count_info.acc_count = props.accIdsList.length
205
+    loadPageRuleChange()
206
+  })
127 207
 }
128 208
 // 暴露自己的属性供父组件使用
129 209
 defineExpose({

+ 209 - 0
src/components/businessMoudle/batchGdt/configArea/landPage/landPageList.vue

@@ -0,0 +1,209 @@
1
+<template>
2
+  <div class="content-wrap" v-loading="loading">
3
+
4
+    <div class="filter-wrap">
5
+      <div class="filter-item flex">
6
+        <el-input style="width: 260px;" v-model.trim="keyword" placeholder="请输入小程序名称" maxlength="50" clearable
7
+          @keyup.enter.native.prevent.stop="onClickSearch" @clear="onClickSearch">
8
+          <template #append>
9
+            <el-button icon="Search" @click="onClickSearch" />
10
+          </template>
11
+        </el-input>
12
+        <div class="filter-item">
13
+          <el-button type="primary" size="default" text @click="onClickReset">刷新</el-button>
14
+        </div>
15
+      </div>
16
+      <div class="selected-text ml-10">已选择<span class="highlight">{{ multipleSelection?.length || 0 }}</span>条</div>
17
+    </div>
18
+    <el-table ref="multipleTableRef" row-key="id" :data="listData" border height="42vh" :header-row-style="headerRowStyle"
19
+      :header-cell-style="headerCellStyle" :cell-style="cellStyle" @selection-change="handleSelectionChange"
20
+      @select-all="handleSelectionAllChange">
21
+      <el-table-column align="center" type="selection" reserve-selection width="55" fixed="left"
22
+        :selectable="selectableEvent" />
23
+      <el-table-column align="center" prop="appname" label="小程序名称" min-width="100" />
24
+      <el-table-column align="center" prop="appid" label="小程序ID" min-width="100" />
25
+      <el-table-column align="center" prop="applink" label="小程序链接" min-width="200">
26
+        <template #default="{ row }">
27
+          <div class="applink-wrap">
28
+            <div class="link">{{ row.applink }}</div>
29
+            <el-icon class="icon" @click="onClickCopyApplink(row.applink)">
30
+              <DocumentCopy />
31
+            </el-icon>
32
+          </div>
33
+        </template>
34
+      </el-table-column>
35
+      <el-table-column align="center" prop="remarks" label="备注" min-width="100" />
36
+      <el-table-column align="center" prop="account_id" label="媒体账户" min-width="100" />
37
+      <el-table-column align="center" prop="username" label="创建人员" min-width="100" />
38
+      <el-table-column align="center" prop="created_at" label="创建时间" min-width="150" />
39
+    </el-table>
40
+    <div class="pagination-wrap">
41
+      <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.page_size"
42
+        :total="pagination.total" layout="total, prev, pager, next" @current-change="onCurrentChange" />
43
+    </div>
44
+  </div>
45
+</template>
46
+<script setup lang="ts">
47
+import { onBeforeMount, reactive, ref, watch } from 'vue';
48
+import clipboard3 from 'vue-clipboard3'
49
+import to from 'await-to-js'
50
+import { ElMessage } from 'element-plus'
51
+import { getAdcreativesLandingPageList } from './ts/api'
52
+
53
+const props = withDefaults(defineProps<{
54
+  page_type: string,
55
+  select_count: number
56
+}>(), {
57
+})
58
+const emit = defineEmits<{
59
+  (event: "callBack", val?: any): void;
60
+}>();
61
+const keyword = ref('')
62
+const listData = ref<any[]>([])
63
+const loading = ref(false)
64
+const multipleSelection = ref<any[]>([])
65
+const pagination = reactive({
66
+  page: 1,
67
+  page_size: 20,
68
+  total: 0
69
+})
70
+
71
+const headerRowStyle = {
72
+  'height': '44px',
73
+  'font-size': '14px',
74
+  'font-weight': 400,
75
+  'color': '#161E46',
76
+}
77
+const headerCellStyle = {
78
+  'background-color': '#FAFAFA',
79
+}
80
+const cellStyle = {
81
+  'font-size': '14px',
82
+  'color': '#333',
83
+}
84
+onBeforeMount(() => {
85
+  getList()
86
+})
87
+const getList = (account_id?) => {
88
+  loading.value = true
89
+  getAdcreativesLandingPageList({
90
+    account_id: account_id?account_id:'',
91
+    page_type: props.page_type,
92
+    keyword: keyword.value,
93
+    page: pagination.page,
94
+    page_size: pagination.page_size
95
+  }).then((res) => {
96
+    loading.value = false
97
+    if (res) {
98
+      listData.value = res.data;
99
+      pagination.total = res.pageInfo.total;
100
+    }
101
+  }).catch(() => { loading.value = false })
102
+}
103
+const onCurrentChange = (page) => {
104
+  pagination.page = page;
105
+  getList()
106
+}
107
+/**复制链接 */
108
+const onClickCopyApplink = async link => {
109
+  if (!link) return false
110
+  const { toClipboard } = clipboard3()
111
+  const [err] = await to(toClipboard(link))
112
+  if (err) return ElMessage.error('复制失败,请重试')
113
+  ElMessage.success('复制成功')
114
+}
115
+/**选择 */
116
+const handleSelectionChange = (val) => {
117
+  multipleSelection.value = val
118
+  emit('callBack', val)
119
+}
120
+const handleSelectionAllChange = (selection) => {
121
+  if (selection.length > props.select_count && props.select_count != -1) {
122
+    selection.length = props.select_count
123
+  }
124
+  multipleSelection.value = selection
125
+  emit('callBack', selection)
126
+}
127
+const selectableEvent = (row) => {
128
+  if (multipleSelection.value.map((v) => { return v.appid }).includes(row.appid)) {
129
+    return true
130
+  }
131
+  if (multipleSelection.value.length == props.select_count && props.select_count != -1) {
132
+    return false
133
+  } else {
134
+    return true
135
+  }
136
+}
137
+/**点击搜索 */
138
+const onClickSearch = () => {
139
+  getList()
140
+}
141
+/**刷新 */
142
+const onClickReset = () => {
143
+  getList()
144
+}
145
+watch(
146
+  () => props.select_count
147
+, (newValue, oldValue) => {
148
+  handleSelectionAllChange(multipleSelection.value)
149
+}, { immediate: true})
150
+/**初始化 */
151
+const initFun = (account_id) => {
152
+  getList(account_id)
153
+}
154
+// 暴露自己的属性供父组件使用
155
+defineExpose({
156
+  initFun
157
+});
158
+</script>
159
+<style scoped lang="scss">
160
+.content-wrap {
161
+  :deep(.el-table__cell) {
162
+    font-size: 13px !important;
163
+  }
164
+}
165
+
166
+.filter-wrap {
167
+  display: flex;
168
+  align-items: center;
169
+  justify-content: space-between;
170
+  background-color: #f8f8f8;
171
+  border: 1px solid #ebeef5;
172
+  border-bottom: none;
173
+  padding: 10px;
174
+}
175
+
176
+.applink-wrap {
177
+  display: flex;
178
+  align-items: center;
179
+  justify-content: center;
180
+
181
+  .link {
182
+    overflow: hidden;
183
+    text-overflow: ellipsis;
184
+    white-space: nowrap;
185
+  }
186
+
187
+  .icon {
188
+    font-size: 16px;
189
+    color: #3173FF;
190
+    margin-left: 6px;
191
+    cursor: pointer;
192
+  }
193
+}
194
+
195
+.pagination-wrap {
196
+  padding: 10px 0;
197
+  display: flex;
198
+  justify-content: center;
199
+}
200
+
201
+.selected-text {
202
+  font-size: 14px;
203
+  color: #666;
204
+
205
+  .highlight {
206
+    color: #3173FF;
207
+    margin: 0 4px;
208
+  }
209
+}</style>

+ 19 - 0
src/components/businessMoudle/batchGdt/configArea/landPage/ts/api.ts

@@ -0,0 +1,19 @@
1
+import http from '@/http/http'
2
+import { ElMessage } from "element-plus";
3
+
4
+/**落地页列表 */
5
+interface IGetAdcreativesLandingPageList {
6
+  account_id: string,
7
+  page_type: string,
8
+  keyword: string,
9
+  page: number,
10
+  page_size: number
11
+}
12
+export async function getAdcreativesLandingPageList(params: IGetAdcreativesLandingPageList) {
13
+  const res: any = await http.get('/api/adcreatives/landingPageList', params)
14
+  if (res.errNo == 0 && res.rst) {
15
+    return res.rst
16
+  } else {
17
+    return
18
+  }
19
+}

+ 0 - 48
src/components/businessMoudle/batchGdt/configArea/landPage/writerHelper.vue

@@ -1,48 +0,0 @@
1
-<template>
2
-<el-dialog class="gdt-dialog" :close-on-click-modal="false" v-model="visible" title="创意文案" width="1000px" top="40px"
3
-:before-close="handleClose">
4
-  <TextLibrary source="writerHelper" :select_num="1" @select="selectEvent"></TextLibrary>
5
-  <template #footer>
6
-    <div class="dialog-footer" style="text-align: right;padding:10px">
7
-      <el-button @click="handleClose"> 取 消 </el-button>
8
-      <el-button type="primary" @click="submitEvent"> 确 定 </el-button>
9
-    </div>
10
-  </template>
11
-</el-dialog>
12
-</template>
13
-<script setup lang="ts">
14
-import TextLibrary from '@/components/businessMoudle/textLibrary/index.vue'
15
-import { ElMessage } from "element-plus";
16
-import { ref } from 'vue'
17
-const visible = ref(false)
18
-const text_info = ref<any[]>([])
19
-
20
-const emit = defineEmits<{
21
-  (event: "close", val?: any): void;
22
-}>();
23
-
24
-const handleClose = () => {
25
-  visible.value = false
26
-}
27
-const submitEvent = () => {
28
-  if(text_info.value.length <= 0){
29
-    ElMessage.warning('请选择文案!')
30
-    return
31
-  }
32
-  visible.value = false
33
-  emit('close', text_info.value)
34
-}
35
-const selectEvent = (val) => {
36
-  text_info.value = val
37
-}
38
-
39
-/**初始化 */
40
-const initFun = (flag) => {
41
-  visible.value = flag
42
-}
43
-// 暴露自己的属性供父组件使用
44
-defineExpose({
45
-  initFun
46
-});
47
-
48
-</script>

+ 13 - 11
src/components/businessMoudle/batchGdt/configArea/originalityBasic/index.vue

@@ -133,7 +133,7 @@ const site_set = ref<string[]>([]); // 投放版位
133 133
 const loading = ref<boolean>(true)
134 134
 const conLoading = ref<boolean>(false)
135 135
 const fullscreenLoading = ref<boolean>(false)
136
-const templateType = reactive({
136
+const templateType = reactive<{img: any[],video:any[]}>({
137 137
   img: [],
138 138
   video: []
139 139
 })
@@ -171,7 +171,7 @@ const radioChange = (obj: object) => {
171 171
     }
172 172
     form_main[i] = obj[i]
173 173
 
174
-    if (i == 'originalityForm'){//创意形式
174
+    if (i == 'originalityForm' && form_main['originalityForm'] && form_main['originalityForm'] != ''){//创意形式
175 175
       templateCurr.value = templateType[form_main['originalityForm']][0]
176 176
       getAdcreativeTemplateInfo()
177 177
     }
@@ -184,8 +184,6 @@ const backhaul = (obj) => {
184 184
 }
185 185
 /**确认 */
186 186
 const submitEvent = () => {
187
-  // console.log(form)
188
-  // return;
189 187
   let promise_arr: any = []
190 188
   for (let i in form) {//检索是否有未填写信息
191 189
     if (form[i].required && (!form[i].api_value || JSON.stringify(form[i].api_value) == "{}" || form[i].api_value == '')) { // required 和 api_value来判断是否填写需要的信息
@@ -391,10 +389,6 @@ const initEvent = () => {
391 389
   if (Array.isArray(apiFormList.value) && apiFormList.value.length > 0) {
392 390
     return
393 391
   }
394
-  apiFormList.value = []
395
-  props.accIdsList.forEach((item) => {
396
-    apiFormList.value.push({ account_id: item.id, adcreative_elements: {} })
397
-  })
398 392
   if (props.adBasicInfo.automatic_site == '1') {// 广告版位 - 自动版位  除视频号版位,其他都用作投放版位
399 393
     site_set.value = Object.keys(specificPositionAll()).filter((v) => { return v != 'SITE_SET_MOMENTS' && v != 'SITE_SET_WECHAT' })
400 394
   } else {
@@ -407,6 +401,15 @@ const initEvent = () => {
407 401
     site_set: site_set.value,
408 402
     is_automatic_site: props.adBasicInfo.automatic_site == '1' ? 1 : 0
409 403
   }).then((res) => { // 创意形式
404
+    loading.value = false;
405
+    if(!Array.isArray(res) || res.length == 0){ 
406
+      ElMessage.warning('不存在任意创意形式!')
407
+      return
408
+    }
409
+    apiFormList.value = []
410
+    props.accIdsList.forEach((item) => {
411
+      apiFormList.value.push({ account_id: item.id, adcreative_elements: {} })
412
+    })
410 413
     let typeList: any = []
411 414
     templateType.video = res.filter((v) => { return v.adcreative_template_style == '视频' })
412 415
     templateType.img = res.filter((v) => { return v.adcreative_template_style == '图片' })
@@ -420,7 +423,6 @@ const initEvent = () => {
420 423
     } else {
421 424
       ElMessage.warning('不存在任意创意形式!')
422 425
     }
423
-    loading.value = false;
424 426
   }).catch(() => { loading.value = false })
425 427
 }
426 428
 const switchShow = async (val:boolean, obj:any)=>{
@@ -432,8 +434,8 @@ const switchShow = async (val:boolean, obj:any)=>{
432 434
       fillBackInfo.value = JSON.parse(JSON.stringify(obj.fillback))
433 435
       templateCurr.value = JSON.parse(JSON.stringify(obj?.apiResult?.templateCurr))
434 436
       adTemplateItem.value = JSON.parse(JSON.stringify(obj?.apiResult?.adTemplateItem))
435
-      console.log('apiFormList',apiFormList.value)
436
- console.log('fillBack',fillBack)
437
+//       console.log('apiFormList',apiFormList.value)
438
+//  console.log('fillBack',fillBack)
437 439
       nextTick(()=>{
438 440
         DynamicCreativeGroupUsedRef.value!.switchShow(fillBack.dynamic_creative_group_used)
439 441
         BrandRef.value!.switchShow({

+ 40 - 14
src/components/businessMoudle/miniprogram/index.vue

@@ -2,7 +2,7 @@
2 2
   <div v-loading="loading">
3 3
     <!-- S 筛选 -->
4 4
     <div class="filter-wrap">
5
-      <div class="filter-item">
5
+      <div class="filter-item" v-if="isAccountSearch">
6 6
         <span class="label">媒体账户:</span>
7 7
         <el-select class="w-200" v-model="listParams.account_id" placeholder="全部" clearable @change="onClickSearch" >
8 8
           <el-option
@@ -30,25 +30,27 @@
30 30
     <div class="page-wrap">
31 31
       <!-- S 批量操作 -->
32 32
       <div class="btns-wrap">
33
-        <el-button type="primary" @click="onClickBtns(btns[EBtnsType.BATCH_IMPORT].value)">{{ btns[EBtnsType.BATCH_IMPORT].label }}</el-button>
34
-        <el-select class="w-100 ml-10" placeholder="批量操作" :disabled="!isSelected">
35
-          <el-option
36
-            v-for="btn in btns.options"
37
-            :key="btn.value"
38
-            :label="btn.label"
39
-            :value="btn.value"
40
-            @click="onClickBtns(btn.value)"
41
-          />
42
-        </el-select>
43
-        <div v-show="isSelected" class="selected-text ml-10">已选择<span class="highlight">{{ multipleSelection?.length || 0 }}</span>条</div>
33
+        <template v-if="isOperate">
34
+          <el-button type="primary" @click="onClickBtns(btns[EBtnsType.BATCH_IMPORT].value)">{{ btns[EBtnsType.BATCH_IMPORT].label }}</el-button>
35
+          <el-select class="w-100 ml-10" placeholder="批量操作" :disabled="!isSelected">
36
+            <el-option
37
+              v-for="btn in btns.options"
38
+              :key="btn.value"
39
+              :label="btn.label"
40
+              :value="btn.value"
41
+              @click="onClickBtns(btn.value)"
42
+            />
43
+          </el-select>
44
+        </template>
45
+        <div v-show="!isOperate || isSelected" class="selected-text ml-10">已选择<span class="highlight">{{ multipleSelection?.length || 0 }}</span>条</div>
44 46
       </div>
45 47
       <!-- E 批量操作 -->
46 48
 
47 49
       <!-- S 列表 -->
48 50
       <el-table ref="multipleTableRef" row-key="id" :data="listData.list" border :header-row-style="headerRowStyle" :header-cell-style="headerCellStyle" :cell-style="cellStyle" @selection-change="handleSelectionChange">
49
-        <el-table-column align="center" type="selection" reserve-selection width="55" />
51
+        <el-table-column align="center" type="selection" reserve-selection width="55" fixed="left"/>
50 52
         <el-table-column align="center" prop="appname" label="小程序名称" min-width="100" />
51
-        <el-table-column align="center" label="操作" min-width="100">
53
+        <el-table-column align="center" label="操作" min-width="100" v-if="isOperate">
52 54
           <template #default="{ row }">
53 55
             <el-button type="primary" size="small" text @click="onClickDeleteRow(row)">删除</el-button>
54 56
           </template>
@@ -128,6 +130,17 @@ import useBatchEditDialog from './hooks/useBatchEditDialog'
128 130
 import useBatchImportDialog from './hooks/useBatchImportDialog'
129 131
 import useMultipleTable from './hooks/useMultipleTable'
130 132
 
133
+const props = defineProps({
134
+  isOperate: { // 是否可操作,用于广告-落地页弹框
135
+    type: Boolean,
136
+    default: true
137
+  },
138
+  isAccountSearch: { // 媒体账户搜索是否可搜
139
+    type: Boolean,
140
+    default: true
141
+  }
142
+})
143
+
131 144
 // S 批量操作按钮
132 145
 enum EBtnsType {
133 146
   BATCH_IMPORT = 'batchImport',
@@ -257,6 +270,19 @@ onMounted(() => {
257 270
   handleFetchList()
258 271
 })
259 272
 
273
+
274
+/**初始化 */
275
+const exposeEvent = ({ account_id }:{ account_id: string }) => {
276
+  if( account_id ){
277
+    listParams.account_id = account_id;
278
+    onClickSearch()
279
+  }
280
+}
281
+// 暴露自己的属性供父组件使用
282
+defineExpose({ 
283
+  exposeEvent
284
+});
285
+
260 286
 </script>
261 287
 
262 288
 <style lang="scss" scoped>