xiuli.gao 1 rok temu
rodzic
commit
447a278f30

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

@@ -8,7 +8,7 @@
8 8
   cursor: no-drop;
9 9
 }
10 10
 .pointerDrop{
11
-  cursor: no-drop;
11
+  cursor: no-drop !important;
12 12
 }
13 13
 .pointer-move{
14 14
   cursor: move;

+ 26 - 2
src/components/businessMoudle/batchGdt/configArea/index.vue

@@ -2,8 +2,10 @@
2 2
   <div class="flex titleBox">
3 3
     <span>程序化批量/广点通</span>
4 4
   </div>
5
-  <div class="areaBox">
6
-    <div class="areaTitle">配置区
5
+  <div class="areaBox" style="margin-top:10px;">
6
+    <div class="areaTitle">
7
+      配置区
8
+      <el-button class="lMar20" type="primary" @click="openStrategyGroups">选择策略组</el-button>
7 9
       <span class="smallTitle lMarauto">预览广告数 : <span class="c-theme">{{NumberHandle(pageInfo.num_total.adNum)||0}}</span></span>
8 10
     </div>
9 11
     <div class="areaCon">
@@ -104,6 +106,7 @@
104 106
         </span>
105 107
         <span class="lMarauto">
106 108
           <el-button type="primary" @click="openPreAreaEvent" v-loading.fullscreen.lock="openPreAreaLoading">批量预览广告</el-button>
109
+          <el-button type="primary" @click="openSaveStrategyGroups" v-loading.fullscreen.lock="openPreAreaLoading">保存策略组</el-button>
107 110
         </span>
108 111
       </div>
109 112
     </div>
@@ -150,6 +153,10 @@
150 153
   <UnionPosition :visible="basicInfoData.outerConfig.unionPosition.visible" :accIdsList="pageInfo.accIdsList" :infoSelect="basicInfoData.outerConfig.unionPosition.infoSelect" :fillback="basicInfoData.outerConfig['unionPosition'].value"  @close="(obj)=>{basicOuterClose({val: obj?.val || null, type: obj?.type || null , key:'unionPosition'})}"></UnionPosition>
151 154
   <!-- 复用配置 -->
152 155
   <ResuseConfig ref="ResuseConfigRef" @aNewConfig="aNewConfigEvent" @reuse="reuseEvent"></ResuseConfig>
156
+  <!-- 策略组列表 -->
157
+  <StrategyGroupsList ref="StrategyGroupsListRef"></StrategyGroupsList>
158
+  <!-- 保存策略组弹框 -->
159
+  <StrategyGroupsDialog ref="StrategyGroupsDialogRef"></StrategyGroupsDialog>
153 160
 </template>
154 161
 <script setup lang="ts">
155 162
 import {getCurrentInstance, nextTick, onMounted, reactive, ref, provide, computed,toRef,watch} from "vue";
@@ -180,6 +187,9 @@ import { resuseConfigEvent} from './ts/resuseConfigTs'
180 187
 import handleJudge from './ts/judgeEvent'
181 188
 import { mockEvent } from './ts/mock'
182 189
 import { AddDays } from "@/common/common";
190
+import StrategyGroupsList from './strategyGroups/list.vue'
191
+import StrategyGroupsDialog from './strategyGroups/saveDialog.vue'
192
+import { strategyGroupsEvent } from './ts/strategyGroups'
183 193
 
184 194
 const { proxy } = getCurrentInstance() as any;
185 195
 // 全局方法定义
@@ -598,6 +608,20 @@ const {
598 608
 })
599 609
 /**E 提交预览 */
600 610
 
611
+/**S 策略组 */
612
+const {
613
+  StrategyGroupsDialogRef,
614
+  StrategyGroupsListRef,
615
+  openStrategyGroups,
616
+  openSaveStrategyGroups
617
+} = strategyGroupsEvent({
618
+  cMaterial,
619
+  basicInfoData,
620
+  pageInfo,
621
+  isMock,
622
+  judgeEvent
623
+})
624
+/**E 策略组 */
601 625
 
602 626
 //传值
603 627
 provide('accountIds',toRef(pageInfo,'accIdsList'))

+ 138 - 0
src/components/businessMoudle/batchGdt/configArea/strategyGroups/list.vue

@@ -0,0 +1,138 @@
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 tMar15 bMar15">
6
+        <Select ref="targetRef" :clearFlag="true" title="推广目标" selectWidth="160px"
7
+        :optObj="{ k: 'name', la: 'desc', val: 'name' }" @changeEvent="targetChange" :options="pageInfo.targetList" />
8
+        <el-input v-model="pageInfo.name" placeholder="搜索策略组名称" style="width: 230px;">
9
+          <template #append>
10
+            <el-button :icon="Search" />
11
+          </template>
12
+        </el-input>
13
+      </div>
14
+      <el-table ref="tableRef" :data="pageInfo?.list" :header-cell-style="tableHeaderStyle"
15
+          border empty-text="暂无数据" max-height="70vh">
16
+          <template v-for="item in pageInfo.descol">
17
+            <el-table-column :fixed="item.fixed" :prop="item.prop" :min-width="item.width ? item.width : '80px'">
18
+              <template #header>
19
+                <div class="flex">
20
+                  <span>{{ item.label }}</span>
21
+                </div>
22
+              </template>
23
+              <template #default="scope">
24
+                <!-- 提交状态 -->
25
+                <div v-if="item.prop == 'status'">
26
+                  <span
27
+                    :class="['task-status', 'rMar10', scope.row.status == 2 && (scope.row.fail_num == 0 ? 'success' : 'fail')]">
28
+                    <span class="icon task-status--finish-icon"></span>
29
+                    <span class="task-status--finish-font">{{ scope.row.status == 0 ? '待提交' : scope.row.status == 1 ?
30
+                      '提交中' :
31
+                      scope.row.status == 2 ? '提交完成' : '' }} </span>
32
+                  </span>
33
+                  <span class="opt-link" v-if="scope.row.status == 2"> 成功:{{ scope.row.success_num }} 失败:{{
34
+                    scope.row.fail_num
35
+                  }} </span>
36
+                </div>
37
+                <!-- 其他 -->
38
+                <div class="cellDiv" v-else>
39
+                  <el-tooltip :disabled="!(scope.row[item.prop] && scope.row[item.prop].length > 30)" effect="dark"
40
+                    :content="scope.row[item.prop] + ''">
41
+                    <div class="clampTwo line21" style="flex: 1">
42
+                      {{ scope.row[item.prop] || scope.row[item.prop] == 0 ? scope.row[item.prop] : '-' }}
43
+                    </div>
44
+                  </el-tooltip>
45
+                </div>
46
+              </template>
47
+            </el-table-column>
48
+          </template>
49
+        </el-table>
50
+    </div>
51
+    <template #footer>
52
+      <div class="dialog-footer" style="text-align: right;padding:10px">
53
+        <el-button @click="handleClose"> 取 消 </el-button>
54
+        <el-button type="primary" @click="submitEvent"> 确 认 </el-button>
55
+      </div>
56
+    </template>
57
+  </el-dialog>
58
+</template>
59
+<script setup lang="ts">
60
+import { Search } from '@element-plus/icons-vue'
61
+import Select from '@/components/capsulationMoudle/_select.vue'
62
+import { getPromotedObjectType } from '@/components/businessMoudle/batchGdt/configArea/basicInfo/ts/basicApi'
63
+import { exportDefine } from '@/components/businessMoudle/adTask/ts/define';
64
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
65
+import { getStrategyGroupsList } from './ts/api'
66
+import { ElMessage } from 'element-plus';
67
+
68
+const emit = defineEmits<{
69
+  (event: "close", val?: any): void;
70
+}>();
71
+const loading = ref(false)
72
+const visible = ref(false)
73
+const targetRef = ref()
74
+const pageInfo = reactive({
75
+  targetList: [],
76
+  targetValue: '',
77
+  name: '',
78
+  descol: [
79
+    { prop: 'radio', label: '', fixed: '' ,width: '60'},
80
+    { prop: 'name', label: '策略组', fixed: '' ,width: '60'},
81
+    { prop: 'promoted_object_type', label: '推广目标', fixed: '', width: '140' },
82
+    { prop: 'note', label: '描述', fixed: '', width: '60' },
83
+    { prop: 'operate', label: '操作', fixed: '' },
84
+  ],
85
+  list:[]
86
+})
87
+
88
+
89
+const handleClose = () => {
90
+  visible.value = false
91
+}
92
+//推广目标
93
+const targetChange = () => {
94
+  pageInfo.targetValue = targetRef.value?.value
95
+}
96
+/**点击确定 */
97
+const submitEvent = () => {
98
+
99
+}
100
+
101
+const {
102
+  tableHeaderStyle,
103
+} = exportDefine()
104
+
105
+/**获取策略组列表 */
106
+const getList = () => {
107
+  getStrategyGroupsList({
108
+    name: pageInfo.name,
109
+    promoted_object_type: pageInfo.targetValue
110
+  }).then((res)=>{
111
+    console.log(res)
112
+  })
113
+}
114
+
115
+/**初始化 */
116
+const initFun = async (flag) => {
117
+  visible.value = flag
118
+  if (flag) {
119
+    getList()
120
+    if(pageInfo.targetList.length == 0){ //获取推广目标类型
121
+      await getPromotedObjectType().then((res) => {
122
+        pageInfo.targetList = res
123
+      })
124
+    }
125
+    
126
+  }
127
+}
128
+// 暴露自己的属性供父组件使用
129
+defineExpose({
130
+  initFun
131
+});
132
+</script>
133
+<style lang="scss" scoped>
134
+@import "@/assets/style/batchDialogGdt.scss";
135
+:deep(.spanTitle){
136
+  font-size: 13px;
137
+}
138
+</style>

+ 303 - 0
src/components/businessMoudle/batchGdt/configArea/strategyGroups/saveDialog.vue

@@ -0,0 +1,303 @@
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" class="container">
5
+      <div class="block-title">保存方式</div>
6
+      <div class="lock-wrapper lock-bt-wrapper" style="border:none;padding-top:0">
7
+        <div :class="['lock-bt', 'lock-module']" >
8
+          <el-icon><RefreshRight /></el-icon>
9
+          <span class="rMar3">存为新的策略组</span>
10
+        </div>
11
+        <div :class="['lock-bt', 'lock-module']" >
12
+          <el-icon><RefreshRight /></el-icon>
13
+          <span class="rMar3">更新当前策略组</span>
14
+        </div>
15
+      </div>
16
+      <div class="block-title">选择需要锁定的内容信息</div>
17
+      <div class="part-wrapper">
18
+        <div class="filter-selectors">
19
+          <Select ref="accRef" :clearFlag="true" :isMultiple="true" title="媒体账户" selectWidth="160px"
20
+            :optObj="{ k: 'account_id', la: 'account_id', val: 'account_id' }" :options="groupsInfo.accountList" />
21
+          <Select ref="targetRef" :clearFlag="false" title="推广目标" selectWidth="160px"
22
+            :optObj="{ k: 'name', la: 'desc', val: 'name' }" @changeEvent="targetChange"
23
+            :options="groupsInfo.targetList" />
24
+        </div>
25
+        <div class="lock-wrapper">
26
+          <template v-for="(item, key) in groupsInfo.lockInfo">
27
+            <el-popover placement="top" :width="300" trigger="hover" :disabled="item.click" :content="item.msg">
28
+              <template #reference>
29
+                <div :class="['lock-module', item.lock && 'lock-module-active', !item.click && 'pointerDrop']"
30
+                  @click="clockModlue(key, 'lockInfo')">
31
+                  <div class="lock-module-info">
32
+                    <el-icon class="icon">
33
+                      <Tools />
34
+                    </el-icon>
35
+                    <p class="text-content">{{ item.content }}</p>
36
+                    <p class="text-num">(2)</p>
37
+                  </div>
38
+                  <div class="lock-module-status" v-if="item.lock">
39
+                    <el-icon>
40
+                      <Lock />
41
+                    </el-icon>
42
+                    <span class="lMar3">固定变量</span>
43
+                  </div>
44
+                </div>
45
+              </template>
46
+            </el-popover>
47
+          </template>
48
+        </div>
49
+        <div class="lock-wrapper lock-bt-wrapper">
50
+          <template v-for="(item, key) in groupsInfo.outerConfig">
51
+            <el-popover placement="top" :width="300" trigger="hover" :disabled="item.click" :content="item.msg">
52
+              <template #reference>
53
+                <div v-if="item.show" :class="['lock-bt', 'lock-module', !item.click && 'pointerDrop']" @click="clockModlue(key, 'outerConfig')">
54
+                  <span class="rMar3">{{ item.content }}</span>
55
+                  <el-icon v-if="item.lock">
56
+                    <Lock />
57
+                  </el-icon>
58
+                </div>
59
+              </template>
60
+            </el-popover>
61
+          </template>
62
+        </div>
63
+        <div class="block-title">制定策略组</div>
64
+        <div class="flex_start bMar15">
65
+          <span class="spanTitle spanTitle2">策略组名称</span>
66
+          <el-input
67
+            style="width: 300px"
68
+            v-model="groupsInfo.name"
69
+            maxlength="50"
70
+            placeholder="请输入策略组名称"
71
+            show-word-limit
72
+            type="textarea"
73
+          />
74
+        </div>
75
+        <div class="flex_start bMar15">
76
+          <span class="spanTitle spanTitle2">策略组描述(选填)</span>
77
+          <el-input
78
+            style="width: 300px"
79
+            v-model="groupsInfo.note"
80
+            maxlength="50"
81
+            placeholder="请输入策略组描述"
82
+            show-word-limit
83
+            type="textarea"
84
+          />
85
+        </div>
86
+      </div>
87
+    </div>
88
+    <template #footer>
89
+      <div class="dialog-footer" style="text-align: right;padding:10px">
90
+        <el-button size="default" @click="handleClose"> 取 消 </el-button>
91
+        <el-button size="default" type="primary" @click="submitEvent"> 确 认 </el-button>
92
+      </div>
93
+    </template>
94
+  </el-dialog>
95
+</template>
96
+<script setup lang="ts">
97
+import { Search } from '@element-plus/icons-vue'
98
+import Select from '@/components/capsulationMoudle/_select.vue'
99
+import { getPromotedObjectType } from '@/components/businessMoudle/batchGdt/configArea/basicInfo/ts/basicApi'
100
+import { exportDefine } from '@/components/businessMoudle/adTask/ts/define';
101
+import { nextTick, onBeforeMount, reactive, ref, watch } from 'vue';
102
+import { getStrategyGroupsList } from './ts/api'
103
+import { ElMessage } from 'element-plus';
104
+
105
+const emit = defineEmits<{
106
+  (event: "close", val?: any): void;
107
+}>();
108
+const loading = ref(false)
109
+const visible = ref(false)
110
+const targetRef = ref()
111
+const groupsInfo = reactive({
112
+  accountList: [],
113
+  targetList: [],
114
+  targetValue: '',
115
+  name: '',
116
+  note: '',
117
+  lockInfo: { // num定向包等个数,lock是否选中, click是否可点击,msg不可点击的原因,lock_confine点击此模块之前需要选中的模块
118
+    rule_conf: { content: '规则配置', icon: '', num: '', lock: true, click: true, msg: '' },
119
+    ad_base: { content: '广告基本信息', icon: '', num: '', lock: false, click: true, msg: '' },
120
+    targetings_info: { content: '定向包', icon: '', num: '', lock: false, click: true, msg: '' },
121
+    creative_base: { content: '创意基本信息', icon: '', num: '', lock_confine: ['ad_base'], lock: false, click: false, msg: '' },
122
+    creative_info: { content: '创意素材', icon: '', num: '', lock_confine: ['rule_conf', 'ad_base', 'creative_base'], lock: false, click: false, msg: '' },
123
+    paperwork: { content: '文案', icon: '', num: '', lock_confine: ['ad_base', 'targetings_info', 'creative_base', 'creative_info'], lock: false, click: false, msg: '' },
124
+    landing_page: { content: '落地页', icon: '', num: '', lock_confine: ['ad_base', 'targetings_info', 'creative_base', 'creative_info'], lock: false, click: false, msg: '' },
125
+  },
126
+  outerConfig: {//外部配置 show是否显示
127
+    user_action_sets: { content: '精准匹配归因', show: true, lock: false, lock_confine: ['ad_base'], click: true, msg: '' },
128
+    unionPosition: { content: '优量汇流量包', show: true, lock: false, lock_confine: ['ad_base'], click: true, msg: '' },
129
+    start_audience: { content: '一方助攻人群包', show: true, lock: false, lock_confine: ['ad_base'], click: true, msg: '' },
130
+    wechat_channels: { content: '视频号', show: true, lock: false, lock_confine: ['ad_base', 'creative_base'], click: true, msg: '' },
131
+  },
132
+})
133
+
134
+/**点击保存的模块 */
135
+const clockModlue = (key, type) => {
136
+  if (type == 'lockInfo') {
137
+    if(!groupsInfo.lockInfo[key].click) return
138
+    groupsInfo.lockInfo[key].lock = !groupsInfo.lockInfo[key].lock
139
+    changeMsgAddClick('lockInfo')
140
+  }
141
+  if(type == 'outerConfig'){
142
+    if(!groupsInfo.outerConfig[key].click) return
143
+    groupsInfo.outerConfig[key].lock = !groupsInfo.outerConfig[key].lock
144
+    changeMsgAddClick('outerConfig')
145
+  }
146
+}
147
+/** 更新msg及click */
148
+const changeMsgAddClick = (type) => {
149
+  //外部配置,lock_confine限制的是lockInfo里的,更新msg及click 
150
+  for (let key in groupsInfo[type]) {//更新msg及click
151
+    let item = groupsInfo[type][key]
152
+    item.click = true;
153
+    item.msg = '';
154
+    if (item && !item.lock && item.lock_confine && Array.isArray(item.lock_confine)) {
155
+      for (let i = 0; i < item.lock_confine.length; i++) {
156
+        let v_item = groupsInfo.lockInfo[item.lock_confine[i]]
157
+        if (!v_item?.lock) {
158
+          item.msg = `锁定${v_item.content}后才能锁定该模块`
159
+          item.click = false
160
+          break;
161
+        }
162
+      }
163
+    }
164
+  }
165
+}
166
+
167
+/**点击关闭弹框 */
168
+const handleClose = () => {
169
+  visible.value = false
170
+}
171
+//推广目标
172
+const targetChange = () => {
173
+  // pageInfo.targetValue = targetRef.value?.value
174
+}
175
+/**点击确定 */
176
+const submitEvent = () => {
177
+
178
+}
179
+
180
+const {
181
+  tableHeaderStyle,
182
+} = exportDefine()
183
+
184
+/**获取策略组列表 */
185
+const getList = () => {
186
+
187
+}
188
+
189
+/**初始化 */
190
+const initFun = async (flag, obj) => {
191
+  visible.value = flag
192
+  if (flag) {
193
+    console.log(obj);//到这里了
194
+    changeMsgAddClick('lockInfo')
195
+    changeMsgAddClick('outerConfig')
196
+  }
197
+}
198
+// 暴露自己的属性供父组件使用
199
+defineExpose({
200
+  initFun
201
+});
202
+</script>
203
+<style lang="scss" scoped>
204
+@import "@/assets/style/batchDialogGdt.scss";
205
+
206
+:deep(.spanTitle) , .spanTitle{
207
+  font-size: 13px;
208
+  color: #666;
209
+}
210
+.container{
211
+  max-height: 73vh;
212
+  overflow-y: auto;
213
+  height: auto !important;
214
+  border-bottom: 1px solid #f2f2f2;
215
+}
216
+.spanTitle2{
217
+  display: inline-block;
218
+  width: 110px;
219
+  margin-top: 10px;
220
+}
221
+
222
+.block-title {
223
+  margin: 30px 0 16px;
224
+  font-size: 14px;
225
+  color: #333;
226
+}
227
+
228
+.filter-selectors {
229
+  display: flex;
230
+  align-items: center;
231
+}
232
+
233
+.lock-wrapper {
234
+  display: flex;
235
+  align-items: center;
236
+  justify-content: space-between;
237
+  // flex-direction: row;
238
+  // flex-wrap: wrap;
239
+  text-align: center;
240
+  margin-top: 20px;
241
+  padding-top: 20px;
242
+  border-top: 1px solid #f2f2f2;
243
+}
244
+
245
+.lock-module {
246
+  flex-shrink: 0;
247
+  width: 120px;
248
+  height: 160px;
249
+  cursor: pointer;
250
+  user-select: none;
251
+  border: 1px solid #d0d0d0;
252
+  border-radius: 6px;
253
+
254
+  &:hover {
255
+    box-shadow: 0 0 5px 1px #e8e8e8;
256
+  }
257
+
258
+  .lock-module-info {
259
+    height: 128px;
260
+    padding-top: 40px;
261
+    font-size: 14px;
262
+    line-height: 20px;
263
+
264
+    .icon {
265
+      font-size: 20px;
266
+    }
267
+
268
+    .text-content {
269
+      margin-top: 16px;
270
+    }
271
+
272
+    .text-num {}
273
+  }
274
+
275
+  .lock-module-status {
276
+    display: flex;
277
+    align-items: center;
278
+    font-size: 12px;
279
+    justify-content: center;
280
+    margin-top: 6px;
281
+  }
282
+
283
+  &.lock-module-active {
284
+    color: #197afb;
285
+    border-color: #197afb;
286
+
287
+    &:hover {
288
+      box-shadow: 0 0 5px 0 #197afb;
289
+    }
290
+  }
291
+}
292
+.lock-bt-wrapper{
293
+  justify-content: start;
294
+.lock-bt{
295
+  width: 160px;
296
+    height: 48px;
297
+    display: flex;
298
+    align-items: center;
299
+    justify-content: center;
300
+    margin-right: 15px;
301
+}
302
+}
303
+</style>

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

@@ -0,0 +1,19 @@
1
+import http from '@/http/http'
2
+import { ElMessage, ElMessageBox } from "element-plus";
3
+
4
+/** 策略组列表 */
5
+interface IStrategyGroupsList {
6
+  name?: string,
7
+  promoted_object_type?: string,
8
+}
9
+export function getStrategyGroupsList(params: IStrategyGroupsList) {
10
+  return new Promise(async (resolve,reject)=>{
11
+    const res: any = await http.get('/api/ad/strategyGroupsList', params)
12
+    if (res.errNo == 0) {
13
+      resolve(res.rst)
14
+    } else {
15
+      ElMessage.error(res.errMsg)
16
+      reject()
17
+    }
18
+  })
19
+}

+ 38 - 0
src/components/businessMoudle/batchGdt/configArea/ts/strategyGroups.ts

@@ -0,0 +1,38 @@
1
+import { ref } from "vue"
2
+
3
+interface IStrategyGroupsEvent {
4
+  judgeEvent: (...args: any[]) => boolean,
5
+  [propName: string]: any
6
+}
7
+
8
+export const strategyGroupsEvent = ({
9
+  cMaterial,
10
+  basicInfoData,
11
+  pageInfo,
12
+  isMock,
13
+  judgeEvent
14
+}:IStrategyGroupsEvent) => {
15
+  const StrategyGroupsListRef = ref()
16
+  const StrategyGroupsDialogRef = ref()
17
+  /**打开策略组列表 */
18
+  const openStrategyGroups = () => {
19
+    StrategyGroupsListRef.value?.initFun(true)
20
+  }
21
+
22
+  /**打开保存策略组弹框 */
23
+  const openSaveStrategyGroups = () => {
24
+    if (!judgeEvent(-1) && !isMock.value) { return }
25
+    StrategyGroupsDialogRef.value?.initFun(true, {
26
+      cMaterial,
27
+      basicInfoData,
28
+      pageInfo,
29
+    })
30
+  }
31
+
32
+  return {
33
+    StrategyGroupsDialogRef,
34
+    StrategyGroupsListRef,
35
+    openStrategyGroups,
36
+    openSaveStrategyGroups
37
+  }
38
+}