liuxiaona 1 year ago
parent
commit
db338aa974

+ 1 - 1
src/assets/style/index.scss

@@ -82,7 +82,7 @@ $primary-color: #3173FF!important;
82 82
 }
83 83
 .el-dialog__footer{
84 84
   text-align: center;
85
-  padding: 10px;
85
+  padding: 15px;
86 86
 }
87 87
 :root{
88 88
   --el-border-color: #d9d9d9;

+ 4 - 0
src/assets/style/self.scss

@@ -339,6 +339,10 @@
339 339
   align-items: center;
340 340
   justify-content: space-between;
341 341
 }
342
+.flex_start{
343
+  display: flex;
344
+  align-items: flex-start;
345
+}
342 346
 .flexWrap {
343 347
   display: flex;
344 348
   align-items: center;

+ 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;

+ 47 - 9
src/components/businessMoudle/batchGdt/configArea/copywriter/index.vue

@@ -1,6 +1,6 @@
1 1
 <template>
2 2
   <div class="flex">
3
-    <el-select v-model="selectValue" placeholder="请选择">
3
+    <el-select v-model="selectValue" placeholder="请选择" @change="selectValueChange">
4 4
       <el-option
5 5
         v-for="item in selectOptions"
6 6
         :key="item.value"
@@ -13,7 +13,11 @@
13 13
       <span><el-icon class="questionFilled"><QuestionFilled /></el-icon></span>
14 14
     </el-tooltip>
15 15
   </div>
16
-  <WriterDialog ref="WriterDialogRef"></WriterDialog>
16
+  <div class="add-sec" v-if="writer_list?.length > 0">
17
+    <p class="add-sec-title">文案:</p>
18
+    <div class="add-sec-span" v-for="item in writer_list">{{ item }}</div>
19
+  </div>
20
+  <WriterDialog ref="WriterDialogRef" @close="closeWriterDialog"></WriterDialog>
17 21
 </template>
18 22
 <script setup lang="ts">
19 23
 import { nextTick, ref } from 'vue';
@@ -22,20 +26,31 @@ const selectOptions = [
22 26
   { label: '自动分配', value: 1 },
23 27
   { label: '程序化测试', value: 2 },
24 28
 ]
25
-const props = defineProps({
26
-  showFlag: {
27
-    type: Boolean,
28
-    default: false
29
-  }
30
-})
29
+const writer_list = ref<any[]>([])
30
+
31
+const emit = defineEmits<{
32
+  (event: "writerCallBack", val?: any): void;
33
+}>();
31 34
 const WriterDialogRef = ref()
32 35
 const selectValue = ref(1)
33 36
 const tooltip = `自动匹配:将文案组分配至广告或创意中。<br/>程序化测试:文案组与定向和创意进行叉乘测试。`
34 37
 
38
+/**分配切换选择 清空选中 */
39
+const selectValueChange = () => {
40
+  closeWriterDialog([])
41
+}
42
+const closeWriterDialog = (obj) => {
43
+  writer_list.value = obj
44
+  emit('writerCallBack',{
45
+    writer: obj,
46
+    allocation_type: selectValue.value
47
+  })
48
+}
49
+
35 50
 /**初始化 */
36 51
 const initFun = (flag) => {
37 52
   nextTick(()=>{
38
-    WriterDialogRef.value!.initFun(flag)
53
+    WriterDialogRef.value!.initFun(flag, selectValue.value)
39 54
   })
40 55
 }
41 56
 // 暴露自己的属性供父组件使用
@@ -49,4 +64,27 @@ defineExpose({
49 64
   font-size: 16px;
50 65
   margin-left: 6px;
51 66
 }
67
+.add-sec-title{
68
+  display: block;
69
+  margin-top: 10px;
70
+  margin-bottom: 8px;
71
+  font-size: 12px;
72
+  color: #666;
73
+}
74
+.add-sec-span{
75
+  display: block;
76
+  padding: 0 8px;
77
+  margin-bottom: 6px;
78
+  overflow: hidden;
79
+  line-height: 32px;
80
+  text-overflow: ellipsis;
81
+  white-space: nowrap;
82
+  background-color: #fafafa;
83
+  border-radius: 2px;
84
+  font-size: 12px;
85
+}
86
+.add-sec{
87
+  max-height: 88%;
88
+  overflow-y: auto;
89
+}
52 90
 </style>

+ 92 - 20
src/components/businessMoudle/batchGdt/configArea/copywriter/writerDialog.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <el-dialog class="gdt-dialog" :close-on-click-modal="false" v-model="visible" title="选择标签" width="1000px" top="40px"
2
+  <el-dialog class="gdt-dialog" :close-on-click-modal="false" v-model="visible" title="选择标签" width="800px" top="40px"
3 3
     :before-close="handleClose">
4 4
     <div v-loading="loading">
5 5
       <div class="flex marT15">
@@ -15,67 +15,131 @@
15 15
           </el-tooltip>
16 16
         </div>
17 17
       </div>
18
-      <div class="flex marT15">
18
+      <div class="flex_start marT15">
19 19
         <label class="form-block-item-title">文案(1-30字)</label>
20
-        <div class="flex">
20
+        <div class="flex" v-if="multiCopyTesting == 0">
21 21
           <div class="input-group">
22 22
             <el-input
23
+              id="emojiInput"
23 24
               class="textarea"
24
-              v-model="textarea"
25
-              :rows="2"
25
+              v-model="text_textarea"
26
+              :rows="4"
27
+              :maxlength="30"
26 28
               type="textarea"
27 29
               placeholder="请输入文案,支持换行输入,建议不超过4行"
28 30
             />
29
-            <span class="input-suffix">0/30</span>
31
+            <span class="input-suffix">{{ text_textarea?.length || 0 }}/30</span>
30 32
             <div class="emoji">
31
-
33
+              <el-popover trigger="hover" width="400">
34
+                <div class="emojiBox">
35
+                  <img class="pointer" v-for="(i,idx) in emojiList" @click="appendText(i)" :alt="i" :title="i"
36
+                       style="width: 28px;height: 28px;margin-right: 6px;margin-bottom: 6px;display: inline-block"
37
+                       :src="'https://wxa.wxs.qq.com/wxad-design/emojis/smiley_'+idx+'.png'" />
38
+                </div>
39
+                <template #reference>
40
+                  <img class="pointer"  src="@/assets/img/icon.png" alt="" width="20">
41
+                </template>
42
+              </el-popover>
32 43
             </div>
33 44
           </div>
45
+          <el-button plain style="margin-left: 10px;" @click="WriterHelperRef!.initFun(true)">文案助手</el-button>
34 46
         </div>
47
+        <div style="color:#999;line-height: 26px;margin-top: 2px;" v-if="multiCopyTesting == 1">已选择 {{ text_list?.length || 0 }} 条,将合并为 {{ text_list?.length || 0 }} 组</div>
35 48
       </div>
49
+      <div class="fix-table" v-if="multiCopyTesting == 1">
50
+        <TextLibrary source="writerHelper" @select="selectEvent"></TextLibrary>
51
+      </div>
52
+
36 53
     </div>
37 54
     <template #footer>
38
-      <span class="dialog-footer">
55
+      <div class="dialog-footer" style="text-align: right;padding:10px">
39 56
         <el-button @click="handleClose"> 取 消 </el-button>
40 57
         <el-button type="primary" @click="submitEvent"> 确 认 </el-button>
41
-      </span>
58
+      </div>
42 59
     </template>
43 60
   </el-dialog>
61
+  <WriterHelper ref="WriterHelperRef" @close="writerHelperClose"></WriterHelper>
44 62
 </template>
45 63
 <script setup lang="ts">
46 64
 import { onBeforeMount, ref } from 'vue';
47
-const visible = ref(false)
48
-const loading = ref(false)
49
-const textarea = ref()
50
-const multiCopyTesting = ref(0)
51
-const tooltip = `您可以选择某一种文案,进行多条文案测试;<br/>测试文案为变量,将会出现在最下方,以列表形式多选;<br/>非测试文案为常量,会保持在上方;`
65
+import {emojiList} from "@/common/emoji";
66
+import TextLibrary from '@/components/businessMoudle/textLibrary/index.vue'
67
+import WriterHelper from './writerHelper.vue'
68
+import { ElMessage } from 'element-plus';
52 69
 const props = defineProps({
53 70
   accIdsList: {
54 71
     type: Array<{ id: string, name: string }>,
55 72
     default: () => []
56
-  },
73
+  }
57 74
 })
58 75
 const emit = defineEmits<{
59 76
   (event: "close", val?: any): void;
60 77
 }>();
78
+
79
+const visible = ref(false)
80
+const loading = ref(false)
81
+const text_textarea = ref()
82
+const text_list = ref<any[]>([])
83
+const multiCopyTesting = ref(0)
84
+const WriterHelperRef = ref()
85
+const allocation = ref(1) // 创意文案分配方式 1自动分配 2程序化测试
86
+const tooltip = `您可以选择某一种文案,进行多条文案测试;<br/>测试文案为变量,将会出现在最下方,以列表形式多选;<br/>非测试文案为常量,会保持在上方;`
87
+
61 88
 onBeforeMount(() => {
62 89
   init()
63 90
 })
64 91
 const init = () => {
65 92
 }
93
+const appendText = (val:any) => {
94
+  var elInput:any = document.getElementById('emojiInput'); //根据id选择器选中对象
95
+  var startPos:any = elInput.selectionStart;// input 第0个字符到选中的字符
96
+  var endPos:any = elInput.selectionEnd;// 选中的字符到最后的字符
97
+  if (startPos === undefined || endPos === undefined) return
98
+  var txt = elInput.value;
99
+  // 将表情添加到选中的光标位置
100
+  var result = txt.substring(0, startPos) + val + txt.substring(endPos)
101
+  elInput.value = result;// 赋值给input的value
102
+  // 重新定义光标位置
103
+  elInput.focus();
104
+  elInput.selectionStart = startPos + val.length;
105
+  elInput.selectionEnd = endPos + val.length;
106
+  text_textarea.value  = result// 赋值给表单中的的字段
107
+}
108
+/**文案助手确定回调 */
109
+const writerHelperClose = (obj) =>{
110
+  if(obj && Array.isArray(obj) && obj.length > 0) {
111
+    text_textarea.value = obj[0].content
112
+  }
113
+}
66 114
 
67 115
 const handleClose = () => {
68 116
   visible.value = false
69 117
 }
70
-
118
+const selectEvent = (obj) => {
119
+  text_list.value = obj.map((v)=>{return v.content})
120
+}
71 121
 /**点击确定 */
72 122
 const submitEvent = () => {
123
+  if(multiCopyTesting.value == 0) {
124
+   if( text_textarea.value == '' || !text_textarea.value ){
125
+    ElMessage.warning('请输入文案!')
126
+    return
127
+   }else{
128
+    text_list.value = [text_textarea.value]
129
+   }
130
+  }
131
+  if(multiCopyTesting.value == 1 && text_list.value?.length <= 0){
132
+    ElMessage.warning('请选择测试的文案内容!')
133
+    return
134
+  }
135
+  emit('close', text_list.value)
73 136
   visible.value = false
74 137
 }
75 138
 
76 139
 /**初始化 */
77
-const initFun = (flag) => {
140
+const initFun = (flag, select) => {
78 141
   visible.value = flag
142
+  allocation.value = select
79 143
 }
80 144
 // 暴露自己的属性供父组件使用
81 145
 defineExpose({
@@ -84,25 +148,33 @@ defineExpose({
84 148
 </script>
85 149
 <style lang="scss" scoped>
86 150
 @import "@/assets/style/batchDialogGdt.scss";
151
+.fix-table{
152
+  padding: 16px;
153
+  border:1px solid #eee;
154
+  border-radius: 4px;
155
+  margin-top: 16px;
156
+}
87 157
 .input-group{
88 158
   position: relative;
89 159
   display: inline;
90 160
 }
91 161
 .textarea{
92 162
   width: 460px;
93
-  padding-right: 50px;
163
+  :deep(.el-textarea__inner){
164
+    padding-right: 50px;
165
+  }
94 166
 }
95 167
 .input-suffix{
96 168
   position: absolute;
97 169
   right: 12px;
98
-  bottom: 0;
170
+  bottom: 6px;
99 171
   margin-top: 2px;
100 172
   line-height: 100%;
101 173
   color: #333;
102 174
 }
103 175
 .emoji{
104 176
   position: absolute;
105
-  top: 5px;
177
+  top: 10px;
106 178
   left: 430px;
107 179
   width: 40px;
108 180
 }

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

@@ -0,0 +1,48 @@
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>

+ 1 - 1
src/components/businessMoudle/batchGdt/configArea/directPacket/newDirecPacket.vue

@@ -279,8 +279,8 @@ const confirmEvent =   () => {
279 279
 //处理编辑的数据,在刚显示弹框的时候调用
280 280
 const handleEditData = async () => {
281 281
   InputRef_name.value!.value = ''
282
+  pageInfoTs.jsonInfo = _.cloneDeep(constant_jsonInfo)
282 283
   if(props.propsInfo.id){ //编辑
283
-    pageInfoTs.jsonInfo = _.cloneDeep(constant_jsonInfo)
284 284
     InputRef_name.value!.value = props.propsInfo.name
285 285
     if(Object.prototype.toString.call(props.propsInfo.echoList) === '[object Object]'){ //有值
286 286
       await editEchoEcent(props.propsInfo.echoList,pageInfoTs.jsonInfo)

+ 135 - 24
src/components/businessMoudle/batchGdt/configArea/index.vue

@@ -40,7 +40,10 @@
40 40
               </div>
41 41
               <div class="showInfoArea">
42 42
                 <template v-if="sub.name == '创意文案'">
43
-                  <Copywriter ref="CopywriterRef" :showFlag="sub.haveContent"></Copywriter>
43
+                  <Copywriter ref="CopywriterRef" @writerCallBack="writerCallBack"></Copywriter>
44
+                </template>
45
+                <template v-if="sub.name == '落地页'">
46
+                  <LandPage ref="LandPageRef" :accIdsList="pageInfo.accIdsList" :page_type="originalityBasicInfoData?.receiveForm?.landingPageExample?.page_type" @landPage="landPageEvent"></LandPage>
44 47
                 </template>
45 48
                 <div class="noData" v-if="!sub.haveContent">暂无数据 </div>
46 49
                 <!-- 定向包展示 -->
@@ -146,6 +149,7 @@ import AttributionOuter from './basicInfo/attributionOuter.vue'
146 149
 import UnionPosition from './basicInfo/unionPosition.vue'
147 150
 import OriginalityBasic from './originalityBasic/index.vue'
148 151
 import Copywriter from './copywriter/index.vue'
152
+import LandPage from "./landPage/index.vue";
149 153
 import { getAdPresets, getPromotedObjectType } from './basicInfo/ts/basicApi'
150 154
 import { FillBackData } from './basicInfo/ts/fillBack'
151 155
 import _ from "lodash";
@@ -162,7 +166,9 @@ const targetRef = ref<{value:any}>()
162 166
 const originalityBasicRef = ref()
163 167
 
164 168
 const pageInfo = reactive<reactiveTableAndAny>({
165
-  adNum:0,
169
+  accNum:0,//账号数
170
+  planNum:0,//计划数
171
+  adNum:0,//预览广告数
166 172
   targetValue: '',
167 173
   targetList:[],
168 174
   adTitleList:[
@@ -173,7 +179,7 @@ const pageInfo = reactive<reactiveTableAndAny>({
173 179
     {id:4,name:'广告创意',subList:[
174 180
         { id:5,name:'创意基本信息',haveContent:false },
175 181
         { id:6,name:'创意素材',chooseNum:0,haveChooseNum:true,haveContent:false },
176
-        { id:7,name:'创意文案',haveContent:false },
182
+        { id:7,name:'创意文案',chooseNum:0,haveChooseNum:true,haveContent:false },
177 183
         { id:8,name:'落地页',chooseNum:0,haveChooseNum:true,haveContent:false },
178 184
       ]},
179 185
   ],
@@ -181,6 +187,14 @@ const pageInfo = reactive<reactiveTableAndAny>({
181 187
   accIdsList:[],//选中的格式化后的账户列表
182 188
   directObj:{},//定向包展示的数据
183 189
   RuleConfigObj:{},//规则配置的值
190
+  copywriterInfoData: {//创意文案
191
+    data: [],
192
+    allocation_type: '',//分配形式 1自动分配 2程序化相乘分配
193
+  },
194
+  copyLandPageInfoData: {//落地页
195
+    data: [],
196
+    allocation_type: '',//分配形式 1自动分配 2程序化相乘分配
197
+  }
184 198
 })
185 199
 
186 200
 
@@ -235,15 +249,7 @@ const originalityBasicInfoData = reactive<any>({
235 249
 })
236 250
 //创意文案
237 251
 const CopywriterRef = ref()
238
-//获取推广目标类型
239
-getPromotedObjectType().then((res)=>{
240
-  pageInfo.targetList = res
241
-  nextTick(()=>{
242
-    targetRef.value!.value = 'PROMOTED_OBJECT_TYPE_LEAD_AD'
243
-    pageInfo.targetValue = targetRef.value!.value
244
-    getAdPresetsInfo(); // 广告预存信息获取
245
-  })
246
-})
252
+const LandPageRef = ref()
247 253
 
248 254
 //获取账号列表
249 255
 const get_account_list = async () => {
@@ -394,14 +400,7 @@ const DirectPacketExhibitionRef = ref<{get_echoAcId:()=>void,
394 400
 
395 401
 // 打开添加 / 编辑
396 402
 const openDialogEvent = async (sub:any) => {
397
-  if(pageInfo.accIdsList && pageInfo.accIdsList.length<=0){
398
-    ElMessage.error('请选择媒体账户!')
399
-    return
400
-  }
401
-  if(!pageInfo.targetValue || pageInfo.targetValue == ''){
402
-    ElMessage.error('请选择推广目标!')
403
-    return
404
-  }
403
+  if( !judgeEvent( sub.id ) ) return; // 判断是否完成前一模块
405 404
   if(sub.name == '定向包'){
406 405
     nextTick(()=>{
407 406
       DirectPacketExhibitionRef.value![0].showDirectPacket()
@@ -451,8 +450,61 @@ const openDialogEvent = async (sub:any) => {
451 450
   if(sub.name == '创意文案'){
452 451
     CopywriterRef.value![0].initFun(true)
453 452
   }
453
+  if(sub.name == '落地页'){
454
+    LandPageRef.value![0].initFun(true)
455
+  }
456
+}
457
+/**每个模块判断是否可以点击添加 id:模块ID*/
458
+const judgeEvent = (id) => {
459
+  if(pageInfo.accIdsList && pageInfo.accIdsList.length<=0){
460
+    ElMessage.error('请选择媒体账户!')
461
+    return false
462
+  }
463
+  if(!pageInfo.targetValue || pageInfo.targetValue == ''){
464
+    ElMessage.error('请选择推广目标!')
465
+    return false
466
+  }
467
+  if(id == 2) return true;//广告基本信息
468
+  if(!basicInfoData.fillBack || basicInfoData.fillBack == '' || JSON.stringify(basicInfoData.fillBack) == '{}'){
469
+    ElMessage.warning('请完善广告基本信息!')
470
+    return false
471
+  }
472
+  if(id == 3) return true;//定向包
473
+  if(id == 5) return true;//创意基本信息
474
+  if(id != 6 && (!pageInfo.directObj || pageInfo.directObj == '' || JSON.stringify(pageInfo.directObj) == '{}')){
475
+    ElMessage.warning('请选择定向包!')
476
+    return false
477
+  }
478
+  if(!originalityBasicInfoData.params || originalityBasicInfoData.params == '' || originalityBasicInfoData.params.length <= 0){
479
+    ElMessage.warning('完善创意基本信息!')
480
+    return false
481
+  }
482
+  if(id == 6) return true;//创意素材 不用判断定向包是否填写
483
+  //  --------   创意素材是否完善判断 -------
484
+  if(id == 7) {//创意文案
485
+    if( !originalityBasicInfoData.apiResult?.adTemplateItem?.adcreative_elements?.description ){
486
+      ElMessage.warning('此创意形式下不可使用创意文案!')
487
+      return false
488
+    } else{
489
+      return true
490
+    }
491
+  };
492
+  // console.log(originalityBasicInfoData.receiveForm.landingPageExample.page_type)
493
+  if(id == 8) {
494
+    if(!originalityBasicInfoData?.receiveForm?.landingPageExample?.page_type){
495
+      ElMessage.warning('此创意形式下不可使用落地页!')
496
+      return false;//落地页
497
+    } else {
498
+      return true
499
+    }
500
+  }
501
+  if(id == -1){ // 提交预览时,判断是否全部完善
502
+  //  --------   创意文案是否完善判断 -------
503
+  //  --------   落地页是否完善判断 -------
504
+  // ----- 外部配置是否完善 -----
505
+  }
506
+  return true
454 507
 }
455
-
456 508
 
457 509
 //更新数值,判断是否有内容
458 510
 const updateHaveContent = (id:number,chooseNum?:number,minusFlag?:boolean,clearFlag?:boolean) => {
@@ -487,15 +539,70 @@ const updateHaveContent = (id:number,chooseNum?:number,minusFlag?:boolean,clearF
487 539
             }
488 540
           }
489 541
         }
542
+
490 543
       })
491 544
     })
492 545
   }
493 546
 }
494 547
 
548
+//计算预览广告数
549
+// let dxb_num = 0;//定向包
550
+// let cysc_num = 0;//创意素材
551
+// let cywa_num = 0;//创意文案
552
+// if(id == 3) { dxb_num = s.chooseNum }
553
+// if(id == 6) { cysc_num = s.chooseNum }
554
+// if(id == 7) { cywa_num = s.chooseNum }
555
+// if( dxb_num > 0 &&  cysc_num > 0){
556
+//   pageInfo.adNum = dxb_num * cysc_num
557
+//   if(pageInfo.allocation_type.allocation_type == 2 && cywa_num > 0) {
558
+//     pageInfo.adNum = pageInfo.adNum * cywa_num
559
+//   }
560
+// }
561
+
562
+/**计算广告数、计划数、账号数 */
563
+const computeCount = (dxb_num, cysc_num, cywa_num) => {
564
+  let accNum = pageInfo.accIdsList.length;
565
+  let adNum = 0;
566
+  adNum = 1 * dxb_num * cysc_num; // 默认为 定向包*创意素材
567
+  if(pageInfo.copywriterInfoData.allocation_type == 2 && cywa_num > 0) {//创意文案 叉乘
568
+    adNum = adNum * cywa_num
569
+  }
570
+  if(pageInfo.copyLandPageInfoData.allocation_type == 2 && pageInfo.copyLandPageInfoData.data && JSON.stringify(pageInfo.copyLandPageInfoData.data) != '{}') {//落地页 叉乘
571
+    // for()
572
+  }
573
+  pageInfo.accNum = accNum;
574
+}
575
+// accNum:0,//账号数
576
+//   planNum:0,//计划数
577
+//   adNum:0,//预览广告数
495 578
 
496 579
 
497
-const init = () => {}
498 580
 
581
+const init = () => {}
582
+/**创意文案回调 */
583
+const writerCallBack = (obj) => {
584
+  pageInfo.copywriterInfoData.data = obj.writer;
585
+  pageInfo.copywriterInfoData.allocation_type = obj.allocation_type;
586
+  if(obj.writer && obj.writer.length > 0){
587
+    updateHaveContent(7,pageInfo.copywriterInfoData.data?.length)
588
+  }else{
589
+    updateHaveContent(7,0)
590
+  }
591
+}
592
+/**落地页回调 */
593
+const landPageEvent = (obj) => {
594
+  let length = 0;
595
+  for(let i in obj.landPage){
596
+    length += obj.landPage[i].length
597
+  }
598
+  pageInfo.copyLandPageInfoData.data = obj.landPage;
599
+  pageInfo.copyLandPageInfoData.allocation_type = obj.allocation_type;
600
+  if(obj.landPage && JSON.stringify(obj.landPage) != '{}') {
601
+    updateHaveContent(8,length)
602
+  }else{
603
+    updateHaveContent(8,0)
604
+  }
605
+}
499 606
 /**创意基本信息回调 */
500 607
 const originalityBasicClose = (obj) => {
501 608
   originalityBasicRef.value!.switchShow(false)
@@ -604,8 +711,12 @@ const getCommonValue = (data) => {
604 711
 
605 712
 onMounted(()=>{
606 713
   nextTick(async ()=>{
607
-    await getPromotedObjectType()
608
-    targetRef.value!.value = pageInfo.targetList[0].id
714
+    //获取推广目标类型
715
+    await getPromotedObjectType().then((res)=>{
716
+      pageInfo.targetList = res
717
+      targetRef.value!.value = pageInfo.targetList[0].name
718
+      pageInfo.targetValue = targetRef.value!.value
719
+    })
609 720
     get_account_list()
610 721
     getAdPresetsInfo(); // 广告预存信息获取
611 722
     get_ruleConfig_info() //规则配置获取

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

@@ -0,0 +1,106 @@
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="callDataInfo[item.id]&&callDataInfo[item.id].length > 0" />
9
+          <i-ep-ArrowRight v-else />
10
+        </el-icon>
11
+      </div>
12
+    </div>
13
+    <div class="tabelCon" v-if="accId">
14
+      <LandPageList ref="LandPageListRef" :key="accId" :account_id="accId" :fillback="copyCallDataInfo" :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 callDataInfo = reactive({})
40
+const accCountInfo = reactive({})
41
+const LandPageListRef = ref()
42
+const copyCallDataInfo = ref([])//copy 解决切换账户,表格回显问题
43
+onMounted(()=>{
44
+  nextTick(()=>{
45
+    props.accIdsList.forEach((item)=>{
46
+      callDataInfo[item.id] = []
47
+      accCountInfo[item.id] = {
48
+        count: -1
49
+      }
50
+    })
51
+    if(props.select_count != -1) {
52
+      //分配条数 accCountInfo
53
+      for(let i in accCountInfo){
54
+        accCountInfo[i].count = props.select_count
55
+      }
56
+    }
57
+    accItemEvent({id: props.accIdsList[0]?.id})
58
+  })
59
+})
60
+/**点击媒体账户 */
61
+const accItemEvent = (item, type?, cb?:Function) => {
62
+  copyCallDataInfo.value = JSON.parse(JSON.stringify(callDataInfo[item?.id]))
63
+  accId.value = item?.id;
64
+}
65
+const handleSelectionChange = (val) => {
66
+  callDataInfo[accId.value] = val
67
+  let msg = '';
68
+  for(let i in callDataInfo){
69
+    if(callDataInfo[i].length == 0){
70
+      msg = `账户${i},请选择落地页!`
71
+      break;
72
+    }
73
+    if(accCountInfo[i]?.count > callDataInfo[i].length && accCountInfo[i]?.count != -1){
74
+      msg = `账户${i},应选择${accCountInfo[accId.value]?.count}个落地页,您的选择数量为:${callDataInfo[i].length}`
75
+      break;
76
+    }
77
+  }
78
+  emit('callBack',{
79
+    data: callDataInfo,
80
+    msg: msg
81
+  })
82
+}
83
+</script>
84
+<style scoped lang="scss">
85
+@import "@/components/businessMoudle/batchGdt/configArea/basicInfo/outer.scss";
86
+.tabelListBox{
87
+  margin-top: 15px;
88
+  height: 56vh;
89
+  .tabelCon{
90
+    overflow: auto;
91
+    height: 100%;
92
+  }
93
+}
94
+.landPageminiprogram{
95
+  :deep(.el-table__cell){
96
+    font-size: 12px !important;
97
+  }
98
+  :deep(.filter-wrap){
99
+    background-color: #f8f8f8;
100
+    padding: 10px ;
101
+  }
102
+  :deep(.page-wrap){
103
+    margin-top: 0;
104
+  }
105
+}
106
+</style>

+ 107 - 0
src/components/businessMoudle/batchGdt/configArea/landPage/index.vue

@@ -0,0 +1,107 @@
1
+<template>
2
+  <div class="flex">
3
+    <el-select v-model="selectValue" placeholder="请选择" @change="selectValueChange">
4
+      <el-option
5
+        v-for="item in selectOptions"
6
+        :key="item.value"
7
+        :label="item.label"
8
+        :value="item.value"
9
+      />
10
+    </el-select>
11
+    <el-tooltip placement="top" effect="light">
12
+      <template #content><span v-html="tooltip"></span></template>
13
+      <span><el-icon class="questionFilled"><QuestionFilled /></el-icon></span>
14
+    </el-tooltip>
15
+  </div>
16
+  <div class="add-sec" v-if="showInfo && JSON.stringify(showInfo) != '{}'">
17
+    <template v-for="(main,main_index) in showInfo">
18
+      <p class="add-sec-title">{{ main_index }}</p>
19
+      <div class="add-sec-span" v-for="item in main">{{ item.appname }}</div>
20
+    </template>
21
+    
22
+  </div>
23
+  <LandPageDialog ref="LandPageDialogRef" @close="closeLandPageDialog" :accIdsList="accIdsList" :page_type="page_type"></LandPageDialog>
24
+</template>
25
+<script setup lang="ts">
26
+import { nextTick, reactive, ref } from 'vue';
27
+import LandPageDialog from './landPageDialog.vue'
28
+
29
+const selectOptions = [
30
+  { label: '自动分配', value: 1 },
31
+  { label: '程序化测试', value: 2 },
32
+]
33
+const props = defineProps({
34
+  showFlag: {
35
+    type: Boolean,
36
+    default: false
37
+  },
38
+  accIdsList: {
39
+    type: Array<{ id: string, name: string }>,
40
+    default: () => []
41
+  },
42
+  page_type: {
43
+    type: String,
44
+    default: ''
45
+  }
46
+})
47
+const emit = defineEmits<{
48
+  (event: "landPage", val?: any): void;
49
+}>();
50
+const showInfo:any = ref({})
51
+const LandPageDialogRef = ref()
52
+const selectValue = ref(1)
53
+const tooltip = `自动匹配:将落地页分配至账户/计划/广告创意中。<br/>程序化测试:落地页与定向、创意、文案进行叉乘测试。`
54
+
55
+const closeLandPageDialog = (obj) => {
56
+  console.log(obj)
57
+  showInfo.value = obj
58
+  emit('landPage',{
59
+    landPage: obj,
60
+    allocation_type: selectValue.value
61
+  })
62
+}
63
+/**分配切换选择 清空选中 */
64
+const selectValueChange = () => {
65
+  closeLandPageDialog({})
66
+}
67
+/**初始化 */
68
+const initFun = (flag) => {
69
+  nextTick(()=>{
70
+    LandPageDialogRef.value!.initFun(flag, selectValue.value)
71
+  })
72
+}
73
+// 暴露自己的属性供父组件使用
74
+defineExpose({
75
+  initFun
76
+});
77
+</script>
78
+<style scoped lang="scss">
79
+.questionFilled{
80
+  color: #ccc;
81
+  font-size: 16px;
82
+  margin-left: 6px;
83
+}
84
+.add-sec-title{
85
+  display: block;
86
+  margin-top: 10px;
87
+  margin-bottom: 8px;
88
+  font-size: 12px;
89
+  color: #666;
90
+}
91
+.add-sec-span{
92
+  display: block;
93
+  padding: 0 8px;
94
+  margin-bottom: 6px;
95
+  overflow: hidden;
96
+  line-height: 32px;
97
+  text-overflow: ellipsis;
98
+  white-space: nowrap;
99
+  background-color: #fafafa;
100
+  border-radius: 2px;
101
+  font-size: 12px;
102
+}
103
+.add-sec{
104
+  max-height: 88%;
105
+  overflow-y: auto;
106
+}
107
+</style>

+ 256 - 0
src/components/businessMoudle/batchGdt/configArea/landPage/landPageDialog.vue

@@ -0,0 +1,256 @@
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
+    <div v-loading="loading">
5
+      <div class="flex marT15">
6
+        <label class="form-block-item-title">分账户选择</label>
7
+        <div class="flex">
8
+          <el-radio-group v-model="multiCopyTesting" size="default">
9
+            <el-radio-button :label="0">不开启</el-radio-button>
10
+            <el-radio-button :label="1">开启</el-radio-button>
11
+          </el-radio-group>
12
+        </div>
13
+      </div>
14
+      <div class="flex marT15" v-if="allocation == 1">
15
+        <label class="form-block-item-title">落地页分配规则</label>
16
+        <el-radio-group v-model="loadPageRule" size="default" @change="loadPageRuleChange">
17
+          <el-radio-button :label="item.id" v-for="item in loadPageRuleList">{{ item.name }}</el-radio-button>
18
+        </el-radio-group>
19
+        <el-tooltip placement="right" effect="light">
20
+          <template #content><span v-html="ruleTooltip"></span></template>
21
+          <span><el-icon class="questionFilled lMar5">
22
+              <QuestionFilled />
23
+            </el-icon></span>
24
+        </el-tooltip>
25
+        <div class="num_pre">
26
+          <span>账户数:</span><span class="num">{{ count_info.acc_count }},</span>
27
+          <span>计划数:</span><span class="num">{{ count_info.plan_count || 0 }},</span>
28
+          <span>广告数:</span><span class="num">{{ count_info.ad_count || 0 }};</span>
29
+        </div>
30
+      </div>
31
+      <div class="flex marT15" v-if="allocation == 2 && multiCopyTesting == 0">
32
+        <label class="form-block-item-title">测试规则</label>
33
+        <el-radio-group v-model="testRuleInfo" size="default">
34
+          <el-radio-button :label="item.id" v-for="item in testRules.list">{{ item.name }}</el-radio-button>
35
+        </el-radio-group>
36
+        <el-tooltip placement="right" effect="light">
37
+          <template #content><span v-html="testRules.msg"></span></template>
38
+          <span><el-icon class="questionFilled lMar5">
39
+              <QuestionFilled />
40
+            </el-icon></span>
41
+        </el-tooltip>
42
+      </div>
43
+      <LandPageList :page_type="page_type" :select_count="allocation == 2 ? -1 : select_count" @callBack="handleSelectionChange"
44
+        v-if="multiCopyTesting == 0" style="margin-top:20px"></LandPageList>
45
+      <AccMinprogram v-if="multiCopyTesting == 1" :page_type="page_type" :select_count="allocation == 2 ? -1 : select_count"
46
+        :accIdsList="accIdsList" @callBack="AccMinprogramChange"></AccMinprogram>
47
+    </div>
48
+    <template #footer>
49
+      <div class="dialog-footer" style="text-align: right;padding:10px">
50
+        <el-button @click="handleClose"> 取 消 </el-button>
51
+        <el-button type="primary" @click="submitEvent"> 确 认 </el-button>
52
+      </div>
53
+    </template>
54
+  </el-dialog>
55
+</template>
56
+<script setup lang="ts">
57
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
58
+import { ElMessage } from 'element-plus';
59
+import LandPageList from './landPageList.vue';
60
+import AccMinprogram from './accMinprogram.vue'
61
+const props = defineProps({
62
+  accIdsList: {
63
+    type: Array<{ id: string, name: string }>,
64
+    default: () => []
65
+  },
66
+  page_type: {
67
+    type: String,
68
+    default: ''
69
+  }
70
+})
71
+const emit = defineEmits<{
72
+  (event: "close", val?: any): void;
73
+}>();
74
+
75
+const count_info = reactive({
76
+  acc_count: 0,
77
+  plan_count: 3,
78
+  ad_count: 10
79
+})
80
+const testRuleInfo = ref(1)
81
+const testRules = {
82
+  msg: `全量测试:各个账户内资产与所有落地页叉乘;<br/>分账户测试:先将落地页均分至各个账户后,与每个账户下的资产分别叉乘;`,
83
+  list: [
84
+    { id: 1, name: '全量测试' },
85
+    { id: 2, name: '分账户测试' },
86
+  ]
87
+}
88
+
89
+const loadPageRuleList = ref<any[]>([])
90
+const loadPageRule = ref(0)
91
+const ruleTooltip = ref<string>()
92
+const visible = ref(false)
93
+const loading = ref(false)
94
+const multiCopyTesting = ref(0)
95
+const allocation = ref(1) // 创意文案分配方式 1自动分配 2程序化测试
96
+let multipleSelection = []
97
+const select_count = ref(-1) //限制选中的条数
98
+const accMinprogramInfo = ref();
99
+
100
+/**自动扩量监听 */
101
+watch(
102
+  () => multiCopyTesting.value
103
+  , (newValue, oldValue) => {
104
+    if (newValue == 0) {
105
+      loadPageRuleList.value = [
106
+        { id: 0, name: '全部相同' },
107
+        { id: 1, name: '按账户分配' },
108
+        { id: 2, name: '分配到计划' },
109
+        { id: 3, name: '分配到广告' },
110
+      ]
111
+      ruleTooltip.value = `全部相同:全部广告使用同一个落地页;<br/>按账户分配:一个账户内广告使用同一个落地页;<br/>分配到计划:一个计划内广告使用同一个落地页;<br/>分配到广告:一个广告一个落地页;`
112
+    }
113
+    if (newValue == 1) {
114
+      loadPageRuleList.value = [
115
+        { id: 0, name: '全部相同' },
116
+        { id: 2, name: '分配到计划' },
117
+        { id: 3, name: '分配到广告' },
118
+      ]
119
+      ruleTooltip.value = `全部相同:全部广告使用同一个落地页;<br/>分配到计划:一个计划内广告使用同一个落地页;<br/>分配到广告:一个广告一个落地页;`
120
+    }
121
+  }, { immediate: true })
122
+
123
+onBeforeMount(() => {
124
+})
125
+const loadPageRuleChange = () => {
126
+  if (allocation.value == 2) {
127
+    select_count.value = -1
128
+    return
129
+  }
130
+  if (loadPageRule.value == 0) {
131
+    select_count.value = 1
132
+  }
133
+  if (loadPageRule.value == 1) {
134
+    select_count.value = count_info.acc_count
135
+  }
136
+  if (loadPageRule.value == 2) {
137
+    select_count.value = count_info.plan_count
138
+  }
139
+  if (loadPageRule.value == 3) {
140
+    select_count.value = count_info.ad_count
141
+  }
142
+}
143
+const handleClose = () => {
144
+  visible.value = false
145
+}
146
+/**点击确定 */
147
+const submitEvent = () => {
148
+  let emitData = {}
149
+  if (multiCopyTesting.value == 1) {//开启
150
+    if(accMinprogramInfo.value.msg != ''){
151
+      ElMessage.warning(accMinprogramInfo.value.msg)
152
+      return
153
+    }
154
+    emitData = accMinprogramInfo.value.data;
155
+    // if (allocation.value == 2) {//程序化叉乘
156
+    //   emitData = accMinprogramInfo.value.data;
157
+    // }
158
+    // if (allocation.value == 1) {//自动分配
159
+    // }
160
+  } else {//不开启
161
+    if (allocation.value == 2) {//程序化叉乘
162
+      if (multipleSelection.length == 0) {
163
+        ElMessage.warning('请选择落地页!')
164
+        return
165
+      }
166
+      if (testRuleInfo.value == 1) {//全量测试
167
+        props.accIdsList.forEach((item) => {
168
+          emitData[item.id] = multipleSelection
169
+        })
170
+      }
171
+      if (testRuleInfo.value == 2) {//分账户测试
172
+        if (multipleSelection.length >= props.accIdsList.length) {
173
+          let len = Math.ceil(multipleSelection.length / props.accIdsList.length)
174
+          props.accIdsList.forEach((item, index) => {
175
+            emitData[item.id] = [].concat(multipleSelection.slice(index * len, (index + 1) * len))
176
+          })
177
+        } else {
178
+          props.accIdsList.forEach((item) => {
179
+            emitData[item.id] = []
180
+          })
181
+          let j = 0;
182
+          for (let i in emitData) {
183
+            if (multipleSelection.length <= j) {
184
+              j = 0
185
+            }
186
+            emitData[i].push(multipleSelection[j])
187
+            j++;
188
+          }
189
+        }
190
+        console.log(emitData, multipleSelection)
191
+      }
192
+    }
193
+    if (allocation.value == 1) {//自动分配
194
+      if(multipleSelection.length < select_count.value){
195
+        ElMessage.warning(`当前应选择${select_count.value}个落地页,您的选择数量为:${multipleSelection.length}`)
196
+        return;
197
+      }
198
+      if(loadPageRule.value == 0){//全部相同
199
+        props.accIdsList.forEach((item) => {
200
+          emitData[item.id] = multipleSelection
201
+        })
202
+      }
203
+      if(loadPageRule.value == 1){//账户数
204
+        props.accIdsList.forEach((item,index) => {
205
+          emitData[item.id] = [multipleSelection[index]]
206
+        })
207
+      }
208
+      if(loadPageRule.value == 2){//计划数 ----- 需要定向包
209
+        props.accIdsList.forEach((item,index) => {
210
+          emitData[item.id] = [multipleSelection[index]]
211
+        })
212
+      }
213
+      if(loadPageRule.value == 3){//广告数 --- 需要定向包
214
+        props.accIdsList.forEach((item,index) => {
215
+          emitData[item.id] = [multipleSelection[index]]
216
+        })
217
+      }
218
+    }
219
+  }
220
+  visible.value = false
221
+  emit('close', emitData)
222
+}
223
+const handleSelectionChange = (val) => {
224
+  multipleSelection = val
225
+}
226
+const AccMinprogramChange = (obj) => {
227
+  accMinprogramInfo.value = obj
228
+}
229
+
230
+/**初始化 */
231
+const initFun = (flag, select) => {
232
+  visible.value = flag
233
+  allocation.value = select
234
+  nextTick(() => {
235
+    count_info.acc_count = props.accIdsList.length
236
+    loadPageRuleChange()
237
+  })
238
+}
239
+// 暴露自己的属性供父组件使用
240
+defineExpose({
241
+  initFun
242
+});
243
+</script>
244
+<style lang="scss" scoped>
245
+@import "@/assets/style/batchDialogGdt.scss";
246
+
247
+.num_pre {
248
+  color: #606266;
249
+  font-size: 12px;
250
+  margin-left: 20px;
251
+
252
+  .num {
253
+    color: #55a1f1;
254
+    font-weight: bold;
255
+  }
256
+}</style>

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

@@ -0,0 +1,225 @@
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="(row)=>row.appid" :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 { nextTick, 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
+  fillback?: any[],
57
+  account_id?: string
58
+}>(), {
59
+})
60
+const emit = defineEmits<{
61
+  (event: "callBack", val?: any): void;
62
+}>();
63
+const keyword = ref('')
64
+const listData = ref<any[]>([])
65
+const loading = ref(false)
66
+const multipleSelection = ref<any[]>([])
67
+const multipleTableRef = ref()
68
+const pagination = reactive({
69
+  page: 1,
70
+  page_size: 20,
71
+  total: 0
72
+})
73
+
74
+const headerRowStyle = {
75
+  'height': '44px',
76
+  'font-size': '14px',
77
+  'font-weight': 400,
78
+  'color': '#161E46',
79
+}
80
+const headerCellStyle = {
81
+  'background-color': '#FAFAFA',
82
+}
83
+const cellStyle = {
84
+  'font-size': '14px',
85
+  'color': '#333',
86
+}
87
+onBeforeMount(() => {
88
+  // getList()
89
+})
90
+const getList = () => {
91
+  loading.value = true
92
+  getAdcreativesLandingPageList({
93
+    account_id: props.account_id?props.account_id:'',
94
+    page_type: props.page_type,
95
+    keyword: keyword.value,
96
+    page: pagination.page,
97
+    page_size: pagination.page_size
98
+  }).then((res) => {
99
+    loading.value = false
100
+    if (res) {
101
+      listData.value = res.data;
102
+      pagination.total = res.pageInfo.total;
103
+    }
104
+  }).catch(() => { loading.value = false })
105
+}
106
+const onCurrentChange = (page) => {
107
+  pagination.page = page;
108
+  getList()
109
+}
110
+/**复制链接 */
111
+const onClickCopyApplink = async link => {
112
+  if (!link) return false
113
+  const { toClipboard } = clipboard3()
114
+  const [err] = await to(toClipboard(link))
115
+  if (err) return ElMessage.error('复制失败,请重试')
116
+  ElMessage.success('复制成功')
117
+}
118
+/**选择 */
119
+const handleSelectionChange = (val) => {
120
+  multipleSelection.value = val
121
+  emit('callBack', val)
122
+}
123
+const handleSelectionAllChange = (selection) => {
124
+  if (selection.length > props.select_count && props.select_count != -1) {
125
+    selection.length = props.select_count
126
+  }
127
+  multipleSelection.value = selection
128
+  emit('callBack', selection)
129
+}
130
+const selectableEvent = (row) => {
131
+  if (multipleSelection.value.map((v) => { return v.appid }).includes(row.appid)) {
132
+    return true
133
+  }
134
+  if (multipleSelection.value.length == props.select_count && props.select_count != -1) {
135
+    return false
136
+  } else {
137
+    return true
138
+  }
139
+}
140
+/**点击搜索 */
141
+const onClickSearch = () => {
142
+  getList()
143
+}
144
+/**刷新 */
145
+const onClickReset = () => {
146
+  getList()
147
+}
148
+watch(
149
+  () => props.select_count
150
+, (newValue, oldValue) => {
151
+  handleSelectionAllChange(multipleSelection.value)
152
+}, { immediate: true})
153
+
154
+watch(
155
+  () => props.account_id
156
+, (newValue, oldValue) => {
157
+  getList()
158
+}, { immediate: true})
159
+
160
+watch([
161
+  () => props.fillback,
162
+  () => multipleTableRef.value
163
+]
164
+, (newValue, oldValue) => {
165
+  if(newValue[0] && newValue[1]){
166
+    if(props.fillback && multipleTableRef.value){
167
+      props.fillback.forEach((row) => {
168
+        multipleTableRef.value?.toggleRowSelection(row, true)
169
+      })
170
+    }
171
+  }
172
+}, { immediate: true})
173
+
174
+</script>
175
+<style scoped lang="scss">
176
+.content-wrap {
177
+  :deep(.el-table__cell) {
178
+    font-size: 13px !important;
179
+  }
180
+}
181
+
182
+.filter-wrap {
183
+  display: flex;
184
+  align-items: center;
185
+  justify-content: space-between;
186
+  background-color: #f8f8f8;
187
+  border: 1px solid #ebeef5;
188
+  border-bottom: none;
189
+  padding: 10px;
190
+}
191
+
192
+.applink-wrap {
193
+  display: flex;
194
+  align-items: center;
195
+  justify-content: center;
196
+
197
+  .link {
198
+    overflow: hidden;
199
+    text-overflow: ellipsis;
200
+    white-space: nowrap;
201
+  }
202
+
203
+  .icon {
204
+    font-size: 16px;
205
+    color: #3173FF;
206
+    margin-left: 6px;
207
+    cursor: pointer;
208
+  }
209
+}
210
+
211
+.pagination-wrap {
212
+  padding: 10px 0;
213
+  display: flex;
214
+  justify-content: center;
215
+}
216
+
217
+.selected-text {
218
+  font-size: 14px;
219
+  color: #666;
220
+
221
+  .highlight {
222
+    color: #3173FF;
223
+    margin: 0 4px;
224
+  }
225
+}</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
+}

+ 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({

+ 1 - 1
src/components/businessMoudle/miniprogram/index.vue

@@ -46,7 +46,7 @@
46 46
 
47 47
       <!-- S 列表 -->
48 48
       <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" />
49
+        <el-table-column align="center" type="selection" reserve-selection width="55" fixed="left"/>
50 50
         <el-table-column align="center" prop="appname" label="小程序名称" min-width="100" />
51 51
         <el-table-column align="center" label="操作" min-width="100">
52 52
           <template #default="{ row }">

+ 36 - 9
src/components/businessMoudle/textLibrary/index.vue

@@ -3,15 +3,17 @@
3 3
     <TypeSelect ref="typeRef2" inputWidth="200px" @change="typeChange"></TypeSelect>
4 4
     <Input class="lMar20" ref="InputRef_text" title="文案" @clearEvent="init()" @changeEvent="init()"/>
5 5
   </div>
6
-  <div class="tableBox">
6
+  <div class="tableBox" :style="source ? 'padding:0' : ''">
7 7
     <div class="flex">
8 8
       <el-button type="primary" @click="addTextEvent">添加文案</el-button>
9
-      <el-button type="primary" plain @click="editSortEvent">修改分类</el-button>
10
-      <el-popconfirm title="确定删除?" @confirm="deleEvent">
11
-        <template #reference>
12
-          <el-button type="primary" plain>删除</el-button>
13
-        </template>
14
-      </el-popconfirm>
9
+      <template v-if="source != 'writerHelper'">
10
+        <el-button type="primary" plain @click="editSortEvent">修改分类</el-button>
11
+        <el-popconfirm title="确定删除?" @confirm="deleEvent">
12
+          <template #reference>
13
+            <el-button type="primary" plain>删除</el-button>
14
+          </template>
15
+        </el-popconfirm>
16
+      </template>
15 17
     </div>
16 18
     <div class="tMar20">
17 19
       <TableList
@@ -21,6 +23,7 @@
21 23
           :total="total"
22 24
           :tableData="tableInfo.tableList"
23 25
           :descol="tableInfo.descolList"
26
+          :selectableEvent="selectableEvent"
24 27
           @init="init"
25 28
           @selectChange="selectEvent"
26 29
       >
@@ -60,7 +63,7 @@ import Popconfirm from '@/components/capsulationMoudle/_popconfirm.vue'
60 63
 import {getCurrentInstance, nextTick, onMounted, reactive, ref, watch} from "vue";
61 64
 import {reactiveTableAndAny, textParam} from "@/api/ApiModel";
62 65
 import {Api} from "@/api/api";
63
-import {ElMessage} from "element-plus";
66
+import {ElMessage, emitChangeFn} from "element-plus";
64 67
 import {publicTableTs} from "@/components/businessMoudle/tableInfo";
65 68
 
66 69
 
@@ -68,7 +71,20 @@ import {publicTableTs} from "@/components/businessMoudle/tableInfo";
68 71
 const { proxy } = getCurrentInstance() as any;
69 72
 // 全局方法定义
70 73
 const NumberHandle = proxy.$NumberHandle
74
+const props = defineProps({
75
+  source:{ // writerHelper创意文案 - 文案助手
76
+    type:String,
77
+  },
78
+  select_num:{
79
+    type: Number,
80
+    default: -1,
81
+  }
82
+})
71 83
 
84
+interface Emits {
85
+  (event: "select", val?:any): void;
86
+}
87
+const emit = defineEmits<Emits>();
72 88
 
73 89
 //修改分类
74 90
 const dialogShow = ref<boolean>(false)
@@ -176,6 +192,7 @@ const selectEvent = (obj:any)=>{
176 192
   obj.forEach(item=>{
177 193
     pageInfo.chooseList.push(item.id)
178 194
   })
195
+  emit('select',obj)
179 196
 }
180 197
 
181 198
 
@@ -222,7 +239,17 @@ const init = async () => {
222 239
     ElMessage.error(res.errMsg)
223 240
   }
224 241
 }
225
-
242
+/**Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 */
243
+const selectableEvent = (row) => {
244
+  if(pageInfo.chooseList.includes(row.id)){
245
+    return true
246
+  }
247
+  if( props.select_num > 0 && pageInfo.chooseList >= props.select_num){
248
+    return false
249
+  }else{
250
+    return true
251
+  }
252
+}
226 253
 onMounted( ()=>{
227 254
   nextTick(async ()=>{
228 255
     await init()

+ 8 - 2
src/components/capsulationMoudle/tableList.vue

@@ -4,7 +4,7 @@
4 4
             @selection-change="selectionChangeEvent" @expand-change="expandChangeEvent" :cell-class-name="cellName" :expand-row-keys="expandArrs"
5 5
             :height="immobilizationHeight"
6 6
             :max-height="tableHeight">
7
-    <el-table-column v-if="showSelect" fixed reserve-selection type="selection" width="60" />
7
+    <el-table-column v-if="showSelect" fixed reserve-selection  :selectable="selectableEvent" type="selection" width="60" />
8 8
     <template v-for="item in descol">
9 9
       <el-table-column :fixed="item.isfixed" :min-width="item.minWidth ? item.minWidth : '80px'">
10 10
         <template #header>
@@ -95,6 +95,7 @@ const props = withDefaults(defineProps<{
95 95
   pageSmall?:boolean,
96 96
   pageJustify?:string,
97 97
   immobilizationHeight?:string,
98
+  selectableEvent?:Function
98 99
 }>(), {
99 100
   tableData: [],
100 101
   descol: [],
@@ -116,7 +117,8 @@ const props = withDefaults(defineProps<{
116 117
   tableSize:'default',
117 118
   pageLayout:'total, prev, pager, next',
118 119
   pageSmall:false,
119
-  pageJustify:'center'
120
+  pageJustify:'center',
121
+  selectableEvent: ()=>{return true}
120 122
 })
121 123
 
122 124
 //表头样式
@@ -176,6 +178,10 @@ const selectionChangeEvent = (val:any) => {
176 178
 const expandChangeEvent = (val:any,expanded:any) => {
177 179
   emit('expandChange',val,expanded) //父组件方法
178 180
 }
181
+//仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选
182
+const selectableEvent = (row,index) => {
183
+  return props.selectableEvent(row) //父组件方法
184
+}
179 185
 
180 186
 //排序事件
181 187
 const sortEvent = (column: string) => {