liuxiaona 1 year ago
parent
commit
490bb76716

+ 362 - 16
src/components/businessMoudle/gdtList/indicators/index.vue

@@ -1,45 +1,211 @@
1 1
 <template>
2
-  <el-dialog class="gdt-dialog" v-model="dialogVisible" :show-close="false" width="1098">
2
+  <el-dialog class="gdt-dialog" v-model="dialogVisible" :destroy-on-close="true" :show-close="false" width="1098" top="60px">
3 3
     <template #header="{ close, titleId, titleClass }">
4 4
       <div class="my-header">
5 5
         <h4 :id="titleId" :class="titleClass">自定义指标</h4>
6 6
       </div>
7 7
     </template>
8 8
     <div class="container">
9
-      <div>主要内容</div>
9
+      <el-input v-model.trim="keyword" @input="searchInput" clearable class="seachWrap" placeholder="输入指标名称搜索">
10
+        <template #append>
11
+          <el-icon>
12
+            <Search />
13
+          </el-icon>
14
+        </template>
15
+      </el-input>
16
+      <div class="indicator-wrapper">
17
+        <div class="indicator-side">
18
+          <div :class="['indicator-category', item.key_value == selectSideBlock ? 'indicator-category--active' : '']"
19
+            v-for="item in indicatorList" @click="onClickSide(item)">{{ item.label }}</div>
20
+        </div>
21
+        <div class="indicator-body">
22
+          <el-checkbox-group v-model="checkList" size="default" @change="changeSelect">
23
+            <div class="indicator-block" :id="`indicators_parend_${item.key_value}`" v-for="item in indicatorList">
24
+              <div class="indicator-group"><span>{{ item.label }}</span></div>
25
+              <el-row :gutter="1">
26
+                <el-col :span="8" v-for="child_item in item.child">
27
+                  <el-checkbox class="checkbox-item" :label="child_item.key_value" :disabled="child_item.disabled == 1">{{
28
+                    child_item.label }}</el-checkbox>
29
+                </el-col>
30
+              </el-row>
31
+            </div>
32
+          </el-checkbox-group>
33
+        </div>
34
+      </div>
10 35
     </div>
11 36
     <div class="indicator-drag">
12 37
       <div class="indicator-content">
13 38
         <div class="drag-title">已选指标</div>
14 39
         <div class="drag-sec">拖动可自定义指标顺序</div>
15 40
         <div class="indicator-limit_low">
16
-          <div class="drag-block">账户ID</div>
41
+          <div class="drag-block" v-for="item in disabledCheck">{{ item.label }}</div>
17 42
         </div>
18 43
         <div class="drag-sepreate">以上指标将横向固定</div>
19 44
       </div>
20 45
       <div class="indicator-limit-many">
21
-        <div class="drag-block">媒体QQ</div>
22
-        <div class="drag-block">媒体QQ</div>
23
-        <div class="drag-block">媒体QQv</div>
46
+        <div class="drag-block mg2" v-for="item in checkAllList">{{ item.label }}</div>
24 47
       </div>
25 48
     </div>
26 49
     <template #footer>
27
-      <span class="dialog-footer">
28
-        <el-button @click="dialogVisible = false">Cancel</el-button>
29
-        <el-button type="primary" @click="dialogVisible = false">
30
-          Confirm
31
-        </el-button>
32
-      </span>
50
+      <div class="dialog-footer flex_between">
51
+        <div class="flex flex_1">
52
+          <el-checkbox style="width:160px;margin-left: 20px;" v-model="saveIndicators" label="保存为常用自定义指标"
53
+            size="default" />
54
+          <el-input v-if="saveIndicators" style="width:260px;margin-left: 8px;" v-model.trim="name"
55
+            placeholder="请输入自定义指标名"></el-input>
56
+        </div>
57
+        <div style="margin-right:30px;">
58
+          <el-button @click="dialogVisible = false">取消</el-button>
59
+          <el-button type="primary" @click="submitEvent">应用</el-button>
60
+        </div>
61
+      </div>
33 62
     </template>
63
+
34 64
   </el-dialog>
35 65
 </template>
36 66
 <script setup lang="ts">
37 67
 import { ref } from 'vue';
38
-import { getAdDefaultKpis } from './ts/api'
68
+import { getAdDefaultKpis, getAdKpisDetails, getAdKpisTemplateSave } from './ts/api'
69
+import { ElLoading, ElMessage } from 'element-plus';
70
+import _ from 'lodash';
71
+
72
+const emit = defineEmits<{
73
+  (event: "saveSuccess", value?: any): void;
74
+}>();
39 75
 
40 76
 const dialogVisible = ref<boolean>(false)
41
-const initFun = (visible) => {
77
+const adType = ref<"media_base" | "campaign_base" | "ad_base" | "creative_base">('media_base')
78
+const keyword = ref<string>('')
79
+const indicatorList = ref<any[]>([])
80
+const copyIndicatorList = ref<any[]>([])
81
+const checkList = ref<any[]>([])//选中指标的key_value
82
+const checkAllList = ref<any[]>([])//携带所有选中指标的信息
83
+const disabledCheck = ref<any[]>([])
84
+const selectSideBlock = ref()
85
+const saveIndicators = ref(false)
86
+const name = ref('')
87
+
88
+/**点击应用 */
89
+const submitEvent = () => {
90
+  if(saveIndicators.value && (name.value == '' || !name.value)){
91
+    ElMessage.warning('请输入自定义指标名')
92
+    return;
93
+  }
94
+  if(checkList.value.length == 0){
95
+    ElMessage.warning('请选择自定义指标')
96
+    return
97
+  }
98
+  getAdKpisTemplateSave({
99
+    temp_type: saveIndicators.value ? 2 : 1,
100
+    name: saveIndicators.value ? name.value : '',
101
+    kpis: checkList.value,
102
+    type: adType.value
103
+  }).then((res)=>{
104
+    console.log('保存成功!', res)
105
+    ElMessage.success('保存成功!')
106
+    dialogVisible.value = false;
107
+    emit('saveSuccess', {
108
+      type: adType.value
109
+    })
110
+  })
111
+}
112
+
113
+/**模糊搜索 */
114
+const searchInput = () => {
115
+  if (keyword.value && keyword.value != '') {
116
+    let arr: any[] = []
117
+    copyIndicatorList.value.forEach((item_main) => {
118
+      let item = _.cloneDeep(item_main)
119
+      item.child.forEach((child_item) => {
120
+        if (child_item.label.indexOf(keyword.value) != -1) {
121
+          let flag_arr = arr.filter((v) => { return v.key_value == item.key_value })
122
+          if (flag_arr.length > 0) {
123
+            flag_arr[0].child.push(child_item)
124
+          } else {
125
+            arr.push(Object.assign(item, { child: [child_item] }))
126
+          }
127
+        }
128
+      })
129
+    })
130
+    indicatorList.value = arr;
131
+  } else {
132
+    indicatorList.value = _.cloneDeep(copyIndicatorList.value)
133
+  }
134
+}
135
+
136
+/**点击左侧 侧边  */
137
+const onClickSide = (info) => {
138
+  selectSideBlock.value = info.key_value;
139
+
140
+  const moduleElement = document.getElementById(`indicators_parend_${info.key_value}`);
141
+  if (moduleElement) {
142
+    moduleElement.scrollIntoView({ behavior: 'smooth' });
143
+  }
144
+}
145
+const changeSelect = () => {
146
+  let str_disabled = disabledCheck.value.map((v) => {
147
+    return v.key_value
148
+  })
149
+  checkAllList.value = []
150
+  checkList.value.forEach((key_value) => {
151
+    indicatorList.value.forEach((item) => {
152
+      let filter_arr = item.child.filter((v) => { return v.key_value == key_value })
153
+      if (filter_arr.length > 0 && str_disabled.indexOf(key_value) == -1) {
154
+        checkAllList.value.push(filter_arr[0])
155
+      }
156
+    });
157
+  })
158
+}
159
+
160
+/**初始化 */
161
+const initFun = (visible, type, temp_id) => {
42 162
   dialogVisible.value = visible
163
+  adType.value = type
164
+  if (visible) {
165
+    const loading = ElLoading.service({
166
+      lock: true,
167
+      text: '加载中',
168
+      background: 'rgba(255,255,255,0.6)',
169
+    })
170
+    getAdDefaultKpis({
171
+      type: adType.value
172
+    }).then((res: any) => {
173
+      if (Array.isArray(res)) {
174
+        saveIndicators.value = false;
175
+        name.value = '';
176
+        indicatorList.value = res;
177
+        copyIndicatorList.value = _.cloneDeep(res)
178
+        if (res.length > 0) {
179
+          selectSideBlock.value = res[0].key_value;
180
+        }
181
+        disabledCheck.value = []
182
+        indicatorList.value.forEach((item) => {
183
+          item.child.forEach((v_item) => {
184
+            if (v_item.disabled == 1) {
185
+              disabledCheck.value.push(v_item)
186
+            }
187
+          });
188
+        });
189
+        // 获取选中值
190
+        getAdKpisDetails({
191
+          type: adType.value,
192
+          temp_id: temp_id ? temp_id : ''
193
+        }).then((kpis_res: any) => {
194
+          loading.close()
195
+          if (kpis_res.kpis) {
196
+            checkList.value = kpis_res.kpis.split(',')
197
+            changeSelect()
198
+          }
199
+        }).catch(() => {
200
+          loading.close()
201
+        })
202
+      } else {
203
+        loading.close()
204
+      }
205
+    }).catch(() => {
206
+      loading.close()
207
+    })
208
+  }
43 209
 }
44 210
 // 暴露自己的属性供父组件使用
45 211
 defineExpose({
@@ -47,8 +213,188 @@ defineExpose({
47 213
 });
48 214
 </script>
49 215
 <style lang="scss" scoped>
50
-.container{
216
+.gdt-dialog .el-dialog__header .el-dialog__title {
217
+  padding-left: 10px !important;
218
+}
219
+
220
+.container {
51 221
   min-height: 300px;
52
-  background: red;
222
+  width: calc(100% - 190px);
223
+}
224
+
225
+.seachWrap {
226
+  width: 300px;
227
+  margin: 15px 0;
228
+}
229
+
230
+.indicator-wrapper {
231
+  display: flex;
232
+  width: 100%;
233
+  height: 60vh;
234
+  border: 1px solid #eaebec;
235
+  border-radius: 4px;
236
+}
237
+
238
+.indicator-side {
239
+  flex-shrink: 0;
240
+  width: 160px;
241
+  overflow: auto;
242
+  border-right: 1px solid #eaebec;
243
+
244
+  .indicator-category {
245
+    padding-left: 16px;
246
+    font-size: 14px;
247
+    line-height: 40px;
248
+    color: #333;
249
+    cursor: pointer;
250
+  }
251
+
252
+  .indicator-category--active {
253
+    color: #197afb;
254
+    background-color: #d6eaff;
255
+  }
256
+}
257
+
258
+.indicator-body {
259
+  flex: 1;
260
+  overflow: auto;
261
+  scroll-behavior: smooth;
262
+
263
+  .indicator-block {
264
+    padding: 16px 0 6px 24px;
265
+    border-bottom: 1px solid #eaebec;
266
+
267
+    .indicator-group {
268
+      display: flex;
269
+      justify-content: flex-start;
270
+      line-height: 20px;
271
+      margin-bottom: 10px;
272
+      font-size: 14px;
273
+      color: #444;
274
+      font-weight: bold;
275
+    }
276
+
277
+    .checkbox-item {}
278
+  }
279
+}
280
+
281
+.indicator-drag {
282
+  position: absolute;
283
+  top: 0;
284
+  right: 0;
285
+  flex-shrink: 0;
286
+  width: 216px;
287
+  // height: 676px;
288
+  padding: 25px 0 10px 0px;
289
+  overflow: auto;
290
+  background-color: #f8f8f9;
291
+  border-right: 1px solid #eaebec;
292
+
293
+  .indicator-content {
294
+    padding: 0 16px;
295
+  }
296
+
297
+  .drag-title {
298
+    font-size: 14px;
299
+    font-weight: 700;
300
+    line-height: 100%;
301
+    color: #333;
302
+  }
303
+
304
+  .drag-sec {
305
+    margin: 8px 0;
306
+    font-size: 12px;
307
+    line-height: 100%;
308
+    color: #999;
309
+  }
310
+
311
+  .drag-sepreate {
312
+    position: relative;
313
+    margin: 16px 0 0;
314
+    font-size: 12px;
315
+    color: #999;
316
+    text-align: center;
317
+
318
+    &::before {
319
+      position: absolute;
320
+      top: 9px;
321
+      left: 0;
322
+      width: 32px;
323
+      height: 1px;
324
+      content: "";
325
+      background-color: #e8eaec;
326
+    }
327
+
328
+    &::after {
329
+      position: absolute;
330
+      top: 9px;
331
+      right: 0;
332
+      width: 32px;
333
+      height: 1px;
334
+      content: "";
335
+      background-color: #e8eaec;
336
+    }
337
+  }
338
+
339
+  .indicator-limit-many {
340
+    max-height: 445px;
341
+    padding: 0 16px;
342
+    margin-top: 16px;
343
+    overflow-x: hidden;
344
+    overflow-y: auto;
345
+  }
346
+
347
+  .drag-block {
348
+    position: relative;
349
+    width: 184px;
350
+    height: 40px;
351
+    padding: 0 30px 0 36px;
352
+    overflow: hidden;
353
+    line-height: 40px;
354
+    text-overflow: ellipsis;
355
+    white-space: nowrap;
356
+    background-color: #fff;
357
+    border-bottom: 1px solid #e8eaec;
358
+
359
+    &::before {
360
+      position: absolute;
361
+      top: 16px;
362
+      left: 12px;
363
+      width: 16px;
364
+      height: 2px;
365
+      content: "";
366
+      background-color: #999;
367
+    }
368
+
369
+    &::after {
370
+      position: absolute;
371
+      bottom: 16px;
372
+      left: 12px;
373
+      width: 16px;
374
+      height: 2px;
375
+      content: "";
376
+      background-color: #999;
377
+    }
378
+  }
379
+}
380
+
381
+.mg2 {
382
+  margin-bottom: 2px;
383
+}
384
+
385
+.el-checkbox {
386
+  color: #444;
387
+  --el-checkbox-font-size: 13px;
388
+  width: 100%;
389
+  white-space: nowrap;
390
+  overflow: hidden;
391
+  text-overflow: ellipsis;
392
+}
393
+
394
+:deep(.el-checkbox__label) {
395
+  width: 100%;
396
+  white-space: nowrap;
397
+  overflow: hidden;
398
+  text-overflow: ellipsis;
53 399
 }
54 400
 </style>

+ 37 - 5
src/components/businessMoudle/gdtList/indicators/ts/api.ts

@@ -1,9 +1,11 @@
1 1
 import http from '@/http/http'
2 2
 import { ElMessage } from "element-plus";
3
-
4
-/**保存常用/自定义指标模板 */
5
-interface IAdKpisTemplateSave {
3
+//基本接口
4
+interface IType {
6 5
   type: 'media_base' | 'campaign_base' | 'ad_base' | 'creative_base',
6
+}
7
+/**保存常用/自定义指标模板 */
8
+interface IAdKpisTemplateSave extends IType {
7 9
   temp_type: 1 | 2,
8 10
   name?: string,
9 11
   kpis: any[]
@@ -21,12 +23,42 @@ export function getAdKpisTemplateSave(params: IAdKpisTemplateSave) {
21 23
 }
22 24
 
23 25
 /**获取所有可选指标 */
24
-interface IAdDefaultKpis {
25
-  type: 'media_base' | 'campaign_base' | 'ad_base' | 'creative_base',
26
+interface IAdDefaultKpis extends IType {
26 27
 }
27 28
 export function getAdDefaultKpis(params: IAdDefaultKpis) {
28 29
   return new Promise( async (resolve, reject) => {
29 30
     const res: any = await http.get('/api/ad/adDefaultKpis', params)
31
+    if (res.errNo == 0 && Array.isArray(res.rst)) {
32
+      resolve(res.rst)
33
+    } else {
34
+      ElMessage.error(res.errMsg)
35
+      reject()
36
+    }
37
+  })
38
+}
39
+
40
+/**获取常用模板下拉 */
41
+interface IGetAdKpisTemplate  extends IType{
42
+}
43
+export function getAdKpisTemplate(params: IGetAdKpisTemplate) {
44
+  return new Promise( async (resolve, reject) => {
45
+    const res: any = await http.get('/api/ad/getAdKpisTemplate', params)
46
+    if (res.errNo == 0 && Array.isArray(res.rst)) {
47
+      resolve(res.rst)
48
+    } else {
49
+      ElMessage.error(res.errMsg)
50
+      reject()
51
+    }
52
+  })
53
+}
54
+
55
+/**获取常用模板下拉 */
56
+interface IGetAdKpisDetails extends IType{
57
+  temp_id?: string
58
+}
59
+export function getAdKpisDetails(params: IGetAdKpisDetails) {
60
+  return new Promise( async (resolve, reject) => {
61
+    const res: any = await http.get('/api/ad/getAdKpisDetails', params)
30 62
     if (res.errNo == 0 && res.rst) {
31 63
       resolve(res.rst)
32 64
     } else {

+ 4 - 2
src/components/businessMoudle/gdtList/plan.vue

@@ -142,7 +142,7 @@
142 142
 
143 143
   <EditIpt ref="planEditIptRef" title="推广计划" @confirm="planEditConfirm"></EditIpt>
144 144
   <TargetEdit ref="TargetEditRef" title="计划设置" @confirm="init"></TargetEdit>
145
-
145
+  <Indicators ref="IndicatorsRef"></Indicators>
146 146
 </template>
147 147
 <script setup lang="ts">
148 148
 import {getCurrentInstance, nextTick, onMounted, reactive, ref} from "vue";
@@ -158,6 +158,7 @@ import {listTs} from "@/components/businessMoudle/gdtList/ts/list.ts";
158 158
 import {Api} from "@/api/api";
159 159
 import {ElMessage} from "element-plus";
160 160
 import {batchGdt_edit, batchGdt_list, reactiveTableAndAny} from "@/api/ApiModel";
161
+import Indicators from './indicators/index.vue'
161 162
 
162 163
 const { proxy } = getCurrentInstance() as any;
163 164
 // 全局方法定义
@@ -247,8 +248,9 @@ const dropdownEvent = (val: string | number | object) => {
247 248
 }
248 249
 
249 250
 //自定义指标
251
+const IndicatorsRef = ref()
250 252
 const customIndEvent = () => {
251
-
253
+  IndicatorsRef.value?.initFun(true, 'media_base')
252 254
 }
253 255
 
254 256
 //修改计划