Browse Source

feat: 客户群分配

zhengxy 1 year ago
parent
commit
b6718c5034

+ 3 - 0
project/src/assets/config/interface_api.js

@@ -485,6 +485,9 @@ var api = {
485 485
 
486 486
   feedback_list: '/api/sys/userOpinionFeedbackList', // 客户意见反馈
487 487
 
488
+  groupTransfer_configList: '/api/chatGroupTransfer/configList', // 客户群分配 - 配置列表
489
+  groupTransfer_changeStatus: '/api/chatGroupTransfer/changeStatus', // 客户群分配 - 切换状态
490
+
488 491
 };
489 492
 
490 493
 export { api };

+ 10 - 0
project/src/components/assembly/screen/channel.vue

@@ -274,6 +274,16 @@ export default {
274 274
         { key: 2, val: '付费' },
275 275
       ]
276 276
       this.placeholderVal = '回传类型'
277
+    } else if (this.type == 'groupTransfer') { // 状态
278
+      this.options = [
279
+        { key: 1, val: '待分配' },
280
+        { key: 2, val: '分配中' },
281
+        { key: 3, val: '分配完成' },
282
+        { key: 4, val: '无目标客户群' },
283
+        { key: 5, val: '分配失败' },
284
+        { key: 0, val: '禁用' },
285
+      ]
286
+      this.placeholderVal = '请选择'
277 287
     } else {
278 288
       this.init()
279 289
     }

+ 10 - 0
project/src/components/assembly/screen/channelMultiple.vue

@@ -254,6 +254,16 @@ export default {
254 254
         { key: 2, val: '付费' },
255 255
       ]
256 256
       this.placeholderVal = '回传类型'
257
+    } else if (this.type == 'groupTransfer') { // 状态
258
+      this.options = [
259
+        { key: 1, val: '待分配' },
260
+        { key: 2, val: '分配中' },
261
+        { key: 3, val: '分配完成' },
262
+        { key: 4, val: '无目标客户群' },
263
+        { key: 5, val: '分配失败' },
264
+        { key: 0, val: '禁用' },
265
+      ]
266
+      this.placeholderVal = '请选择'
257 267
     } else {
258 268
       this.init()
259 269
     }

+ 10 - 0
project/src/components/assembly/screen/channelV2.vue

@@ -517,6 +517,16 @@ export default {
517 517
           { key: 2, val: '付费' },
518 518
         ]
519 519
         this.placeholderVal = '回传类型'
520
+      } else if (this.type == 'groupTransfer') { // 状态
521
+        this.options = [
522
+          { key: 1, val: '待分配' },
523
+          { key: 2, val: '分配中' },
524
+          { key: 3, val: '分配完成' },
525
+          { key: 4, val: '无目标客户群' },
526
+          { key: 5, val: '分配失败' },
527
+          { key: 0, val: '禁用' },
528
+        ]
529
+        this.placeholderVal = '请选择'
520 530
       } else {
521 531
         this.init()
522 532
       }

+ 453 - 0
project/src/components/assembly/screen/chatGroupOptions.vue

@@ -0,0 +1,453 @@
1
+<template>
2
+  <div class="common-screen-item" style="margin-top: 0">
3
+    <el-popover append-to-body placement="bottom" trigger="click" v-model="dialogVisible">
4
+      <div
5
+        slot="reference"
6
+        :style="width ? 'width:' + width : ''"
7
+        :class="['common-screen-self-box', 'common-input-select', chatListResult.length ? 'common-input-select-hover' : '']"
8
+      >
9
+        <div :class="['common-screen-self-con', chatListResult.length ? '' : 'common-screen-self-placeholder']">
10
+          <div class="common-screen-self-con-div">
11
+            <span v-if="chatListResult.length">{{ chatListResultName }}</span>
12
+            <span v-else>请选择群聊</span>
13
+          </div>
14
+        </div>
15
+        <div class="common-screen-self-icon"><i class="el-icon-arrow-down" /></div>
16
+      </div>
17
+
18
+      <div class="flex" style="padding:10px" v-loading="loading">
19
+        <div class="propoverItem">
20
+          <div class="filter-wrap">
21
+            <el-select v-model="owner" class="select-wrap" size="small" placeholder="全部群主" clearable @change="onChangeOwner">
22
+              <el-option v-for="item in ownerOptions" :key="item.user_id" :label="item.name" :value="item.user_id" />
23
+            </el-select>
24
+            <self-input :reset="reset" :hasLabel="false" label_name="群名称" width="180px" style="margin-top:0; margin-bottom:0;" @inputChange="onChangeKeyword" />
25
+          </div>
26
+          <div class="allMember">全部群聊({{ chatList && chatList.length }}):</div>
27
+          <div class="memberBoxBig self-scrollbar-3">
28
+            <div class="memberBox" v-for="(item, idx) in chatList" :key="item.chat_id">
29
+              <div class="meberList">
30
+                <div class="memberItem" @click="handleSwitchChatItem(item, idx)">
31
+                  <div class="flex" style="flex:1">
32
+                    <i class="el-icon-chat-dot-round chat-icon" />
33
+                    <div class="memberInfo">
34
+                      <div class="name">{{ item.name }}</div>
35
+                      <div class="other">群主:{{ item.owner_name }}</div>
36
+                    </div>
37
+                  </div>
38
+                  <div :class="['checkbox', item.isSelected ? 'checkbox_active' : '']">
39
+                    <i class="el-icon-check" />
40
+                  </div>
41
+                </div>
42
+              </div>
43
+            </div>
44
+          </div>
45
+        </div>
46
+
47
+        <div class="line" />
48
+
49
+        <div class="propoverItem" style="padding-left:20px">
50
+          <div class="title">已选择的群聊</div>
51
+          <div class="choice_result_top">
52
+            <div class="clear" @click="handleDelAllSelectedChat">全部清空</div>
53
+            <div class="result_num">已选择<span>{{ chatListSelected.length }}</span>个群</div>
54
+          </div>
55
+          <div class="member_result self-scrollbar-3">
56
+            <div class="memberItem" v-for="(item, idx) in chatListSelected" :key="item.chat_id">
57
+              <div class="flex" style="flex:1">
58
+                <i class="el-icon-chat-dot-round chat-icon" />
59
+                <div class="memberInfo">
60
+                  <div class="name">{{ item.name }}</div>
61
+                  <div class="other">群主:{{ item.owner_name }}</div>
62
+                </div>
63
+              </div>
64
+              <i class="el-icon-close" style="margin-left:10px" @click="handleDelSelectedChat(idx)" />
65
+            </div>
66
+          </div>
67
+          <div class="buttons">
68
+            <el-button type="primary" plain size="mini" @click="dialogVisible = false">取消</el-button>
69
+            <el-button type="primary" size="mini" @click="onClickConfirm">确定</el-button>
70
+          </div>
71
+        </div>
72
+      </div>
73
+    </el-popover>
74
+  </div>
75
+</template>
76
+
77
+<script>
78
+import _lodash from 'lodash'
79
+import selfInput from '@/components/assembly/screen/input.vue'
80
+export default {
81
+  name: "chatGroupOptions",
82
+  components: {
83
+    selfInput,
84
+  },
85
+  props: {
86
+    width: {
87
+      type: String,
88
+      default: () => ''
89
+    },
90
+    isHasRoomId: {
91
+      type: Boolean,
92
+      default: () => false
93
+    },
94
+    isDataIdx: { // 是否开启数据索引
95
+      type: Boolean,
96
+      default: () => false
97
+    },
98
+    dataIdx: { // 当前数据索引
99
+      type: Number,
100
+      default: () => 0
101
+    },
102
+    chatListResult: { // 最终确认选择的群列表
103
+      type: Array,
104
+      default: () => []
105
+    },
106
+  },
107
+  data() {
108
+    return {
109
+      reset: false,
110
+      loading: false,
111
+      dialogVisible: false, // 控制弹框显示
112
+      ownerOptions: [], // 群主筛选
113
+      owner: '', // 群主id
114
+      keyword: '', // 关键字
115
+      chatList: [], // 可选择客户群列表
116
+      chatListSelected: [], // 已选择的群列表
117
+    }
118
+  },
119
+  computed: {
120
+    chatListResultName() {
121
+      const arr = this.chatListResult.map(c => c.name)
122
+      return arr.join(',')
123
+    },
124
+  },
125
+  watch: {
126
+    dialogVisible(isShow) {
127
+      if (isShow) {
128
+        this.handleGetOwnerOptions()
129
+        this.handleGetChatList()
130
+      } else { // 清空搜索关键字
131
+        this.owner = ''
132
+        this.keyword = ''
133
+        this.reset = !this.reset
134
+      }
135
+    },
136
+  },
137
+  methods: {
138
+    // 获取"可选择客户群列表"
139
+    async handleGetChatList() {
140
+      try {
141
+        this.loading = true
142
+        this.chatList = []
143
+        const { data: res = {} } = await this.$axios.get(this.URL.BASEURL + this.URL.groupCode_chatGroupList, {
144
+          params: {
145
+            keyword: this.keyword,
146
+            owner: this.owner,
147
+            has_room_id: this.isHasRoomId ? true : false,
148
+          }
149
+        })
150
+        if (res && res.errno == 0) {
151
+          this.chatList = res.rst.map(item => ({
152
+            ...item,
153
+            name: item.name || '未设置群名',
154
+            isSelected: false
155
+          }))
156
+
157
+          // 如果父组件已有选择的结果 => 根据"最终确认选择的群列表"获取"已选列表"
158
+          if (this.chatListResult.length) {
159
+            this.handleGetChatListSelected()
160
+          }
161
+          // 根据"已选列表" => 回显"可选列表"的已选状态
162
+          this.handleGetIsSelectedChatStatus()
163
+        } else if (res.errno != 4002) {
164
+          this.$message.warning(res.err)
165
+        }
166
+      } catch (error) {
167
+        console.log('error => ', error)
168
+      } finally {
169
+        this.loading = false
170
+      }
171
+    },
172
+    async handleGetOwnerOptions() {
173
+      try {
174
+        this.ownerOptions = []
175
+        const url = `${this.URL.BASEURL}${this.URL.chatGroup_chatGroupOwnerIndex}`
176
+        const params = {}
177
+        const { data: res = {} } = await this.$axios.get(url, { params })
178
+        if (res && res.errno == 0) {
179
+          this.ownerOptions = Array.isArray(res.rst) ? res.rst : []
180
+        } else if (res.errno != 4002) {
181
+          this.$message.warning(res.err)
182
+        }
183
+      } catch (error) {
184
+        console.log('error => ', error)
185
+      }
186
+    },
187
+    onChangeOwner(val) {
188
+      this.owner = val
189
+      this.handleGetChatList()
190
+    },
191
+    onChangeKeyword(val) {
192
+      this.keyword = val
193
+      this.handleGetChatList()
194
+    },
195
+    // 取消可选列表中所有的已选状态
196
+    handleCloseAllChatList() {
197
+      this.chatList.forEach(c => {
198
+        c.isSelected = false
199
+      })
200
+    },
201
+    // 根据"最终确认选择的群列表"获取"已选列表"
202
+    handleGetChatListSelected() {
203
+      this.chatListResult.forEach(item => {
204
+        const isFound = this.chatList.find(c => c.chat_id === item.chat_id)
205
+        if (isFound) {
206
+          const isHas = this.chatListSelected.find(c => c.chat_id === isFound.chat_id)
207
+          !isHas && this.chatListSelected.push(isFound)
208
+        }
209
+      })
210
+    },
211
+    // 根据"已选列表" => 回显"可选列表"的已选状态
212
+    handleGetIsSelectedChatStatus() {
213
+      this.handleCloseAllChatList()
214
+      this.chatListSelected.forEach(item => {
215
+        // 查找当前已选的索引
216
+        const idx = this.chatList.findIndex(chat => chat.chat_id === item.chat_id)
217
+        if (idx !== -1) this.chatList[idx].isSelected = true
218
+      })
219
+    },
220
+    // 监听点击"可选列表 - 项" => 切换已选状态
221
+    handleSwitchChatItem(currentChat, currentIdx) {
222
+      if (currentChat.isSelected) {
223
+        // 已选 => 取消 & 从 chatListSelected 列表中移除
224
+        this.chatList[currentIdx].isSelected = false
225
+        const delIdx = this.chatListSelected.findIndex(chat => chat.chat_id === currentChat.chat_id)
226
+        if (delIdx !== -1) this.chatListSelected.splice(delIdx, 1)
227
+      } else {
228
+        // 未选 => 选择 & 添加到 chatListSelected 列表
229
+        this.chatList[currentIdx].isSelected = true
230
+        this.chatListSelected = [...this.chatListSelected, currentChat]
231
+      }
232
+    },
233
+    // 监听点击"已选列表 - 项" => 删除 & 回显"可选列表"的已选状态
234
+    handleDelSelectedChat(currentDelIdx) {
235
+      this.chatListSelected.splice(currentDelIdx, 1)
236
+      this.handleGetIsSelectedChatStatus()
237
+    },
238
+    // 监听点击"全部清空" => 删除全部 & 回显"可选列表"的已选状态
239
+    handleDelAllSelectedChat() {
240
+      this.chatListSelected = []
241
+      this.handleGetIsSelectedChatStatus()
242
+    },
243
+    // 监听点击"确定"
244
+    onClickConfirm() {
245
+      if (!this.chatListSelected.length) {
246
+        this.$message.warning('请选择群聊')
247
+        return
248
+      }
249
+
250
+      if (this.isDataIdx) {
251
+        this.$emit('change', {
252
+          idx: this.dataIdx,
253
+          res: _lodash.cloneDeep(this.chatListSelected),
254
+        })
255
+      } else {
256
+        this.$emit('change', _lodash.cloneDeep(this.chatListSelected))
257
+      }
258
+
259
+      this.dialogVisible = false
260
+    },
261
+  },
262
+};
263
+</script>
264
+
265
+<style lang="scss" scoped>
266
+.filter-wrap {
267
+  display: flex;
268
+  align-items: center;
269
+  padding-right: 4px;
270
+  .select-wrap {
271
+    margin-right: 4px;
272
+    flex: 1;
273
+  }
274
+}
275
+
276
+.propoverItem {
277
+  width: 300px;
278
+  flex: 1;
279
+  height: 100%;
280
+
281
+  .allMember {
282
+    color: #666666;
283
+    font-size: 14px;
284
+    line-height: 20px;
285
+    margin-top: 17px;
286
+  }
287
+
288
+  .title {
289
+    color: #383e47;
290
+    font-size: 14px;
291
+    line-height: 20px;
292
+    font-weight: bold;
293
+  }
294
+
295
+  .choice_result_top {
296
+    display: flex;
297
+    align-items: center;
298
+    justify-content: space-between;
299
+    margin: 7px 0;
300
+
301
+    .clear {
302
+      color: #00B38A;
303
+      font-size: 13px;
304
+      line-height: 18px;
305
+      cursor: pointer;
306
+      font-weight: bold;
307
+    }
308
+
309
+    .result_num {
310
+      color: #333333;
311
+      font-size: 13px;
312
+      line-height: 18px;
313
+
314
+      span {
315
+        color: #00B38A;
316
+        font-weight: bold;
317
+      }
318
+    }
319
+  }
320
+
321
+  .member_result {
322
+    height: 380px;
323
+    overflow-y: auto;
324
+    padding-right: 15px;
325
+    .memberItem {
326
+      margin: 20px 0;
327
+    }
328
+  }
329
+}
330
+
331
+.line {
332
+  width: 0.5px;
333
+  background: #c3cbd6;
334
+}
335
+
336
+.propoverItem,
337
+.line {
338
+  height: 478px;
339
+}
340
+
341
+.memberBoxBig {
342
+  height: 400px;
343
+  overflow-y: auto;
344
+}
345
+
346
+.memberBox {
347
+  display: flex;
348
+  margin-top: 10px;
349
+
350
+  .el-icon-caret-bottom {
351
+    color: #46a7ff;
352
+    cursor: pointer;
353
+    margin-right: 7px;
354
+    margin-top: 2px;
355
+    transition: all 0.3s;
356
+    display: block;
357
+    height: 14px;
358
+  }
359
+
360
+  .meberList {
361
+    flex: 1;
362
+    margin-right: 15px;
363
+
364
+    .department {
365
+      i {
366
+        color: #46a7ff;
367
+      }
368
+
369
+      span {
370
+        color: #383e47;
371
+        font-size: 14px;
372
+        line-height: 20px;
373
+        margin-left: 5px;
374
+        font-weight: bold;
375
+      }
376
+    }
377
+
378
+    .checkbox {
379
+      width: 15px;
380
+      height: 15px;
381
+      border: 1px solid #979797;
382
+      border-radius: 50%;
383
+      display: flex;
384
+      align-items: center;
385
+      justify-content: center;
386
+      font-size: 12px;
387
+      margin-left: 10px;
388
+
389
+      i {
390
+        color: transparent;
391
+      }
392
+
393
+      &.checkbox_active {
394
+        i {
395
+          color: #ffffff;
396
+        }
397
+
398
+        background: #00B38A;
399
+        border-color: #00B38A;
400
+      }
401
+    }
402
+  }
403
+}
404
+
405
+.memberItem,
406
+.department {
407
+  display: flex;
408
+  align-items: center;
409
+  justify-content: space-between;
410
+  cursor: pointer;
411
+}
412
+
413
+.memberItem {
414
+  margin: 4px 0;
415
+
416
+  .memberImg {
417
+    width: 38px;
418
+    height: 38px;
419
+    border-radius: 4px;
420
+  }
421
+
422
+  .chat-icon {
423
+    font-size: 26px;
424
+  }
425
+
426
+  .memberInfo {
427
+    margin-left: 7px;
428
+    flex: 1;
429
+
430
+    .name {
431
+      color: #333333;
432
+      font-size: 13px;
433
+      line-height: 18px;
434
+    }
435
+
436
+    .other {
437
+      color: #898d92;
438
+      font-size: 12px;
439
+      line-height: 18px;
440
+    }
441
+  }
442
+}
443
+
444
+.reversalAnimation {
445
+  transform: rotate(-90deg);
446
+}
447
+
448
+.buttons {
449
+  text-align: right;
450
+  padding: 20px 0 10px;
451
+}
452
+</style>
453
+

+ 10 - 4
project/src/components/assembly/screen/serviceSingle.vue

@@ -18,8 +18,8 @@
18 18
     <el-dialog title="" :visible.sync="visible" :close-on-click-modal="false" :show-close="false" width="400px" append-to-body center top="15vh">
19 19
       <div v-loading="loading">
20 20
         <div class="propoverItem">
21
-          <self-input :reset='resetFlag' :hasLabel="false" width="90%" style="margin-top:0" @inputChange='(val)=>{input_keyword = val;init("userSearch")}'></self-input>
22
-          <div class="allMember">全部成员({{userTotal}}):</div>
21
+          <self-input :reset='resetFlag' :hasLabel="false" :label_name="source == 'chatGroup' ? '群主' : '成员'" width="90%" style="margin-top:0" @inputChange='(val)=>{input_keyword = val;init("userSearch")}'  />
22
+          <div class="allMember">全部{{ source == 'chatGroup' ? '群主' : '成员' }}({{userTotal}}):</div>
23 23
           <div class="memberBoxBig self-scrollbar-3">
24 24
             <template v-for="(item,index) in userList">
25 25
               <div class="memberBox" :key="index + 'userList' + item.department_id">
@@ -87,7 +87,7 @@ export default {
87 87
     afferent_users: {
88 88
       type: Array
89 89
     },
90
-    source: {
90
+    source: { // 成员许可memberTransfer  客户群chatGroup
91 91
       type: String,
92 92
       default: () => ''
93 93
     },
@@ -163,7 +163,13 @@ export default {
163 163
     },
164 164
     init () {
165 165
       this.loading = true
166
-      this.$axios.get(this.URL.BASEURL + this.URL.userList, {
166
+
167
+      let axios_api = this.URL.userList;
168
+      if (this.source == 'chatGroup') {//群主
169
+        axios_api = this.URL.chatGroup_ownerList
170
+      }
171
+
172
+      this.$axios.get(this.URL.BASEURL + axios_api, {
167 173
         params: {
168 174
           user_name: this.input_keyword
169 175
         }

+ 341 - 0
project/src/components/groupTransfer/configDrawer.vue

@@ -0,0 +1,341 @@
1
+<template>
2
+  <el-drawer
3
+    :visible.sync="visible"
4
+    :close-on-press-escape="false"
5
+    :wrapperClosable="false"
6
+    :withHeader="false"
7
+    size="800px">
8
+    <div class="content-wrap" v-loading="loading">
9
+      <div class="self_drawer_title">
10
+        <div class="flex">
11
+          {{ title }}
12
+        </div>
13
+        <div class="flex-align-center">
14
+          <i class="el-icon-close pointer" @click="handleCloseDrawer" />
15
+        </div>
16
+      </div>
17
+
18
+      <div class="form-wrap" v-loading="loading">
19
+        <div class="form-item">
20
+          <span class="lable required">配置标题</span>
21
+          <el-input placeholder="请输入配置标题" v-model.trim="form.title" clearable size="small" />
22
+        </div>
23
+
24
+        <div class="form-item">
25
+          <span class="lable required">转移类型</span>
26
+          <el-radio v-model="form.type" :label="1">在职迁移</el-radio>
27
+          <el-radio v-model="form.type" :label="2">离职迁移</el-radio>
28
+        </div>
29
+
30
+        <div class="form-item">
31
+          <span class="lable required">转移类型</span>
32
+          <el-radio v-model="form.filter_type" :label="1">按群聊</el-radio>
33
+          <el-radio v-model="form.filter_type" :label="2">按群主</el-radio>
34
+        </div>
35
+
36
+        <div v-show="form.filter_type == 1" class="form-item">
37
+          <span class="lable required">群聊</span>
38
+          <selfChatGroupOptions v-if="isShowService" ref="chatGroupOptionsMain" width="300px" :isHasRoomId="true" :chatListResult="form.chat_ids" @change="onChangeGlobalChatGroup" />
39
+        </div>
40
+
41
+        <div v-show="form.filter_type == 2" class="form-item">
42
+          <span class="lable required">群主</span>
43
+          <selfCustomerservice v-if="isShowService" source="chatGroup" title="" width="400px" placeholder="请选择群主" :reset="reset" :afferent_users="form.old_owners" @customerDefine="onChangeOldOwners" />
44
+        </div>
45
+
46
+        <div class="form-item">
47
+          <span class="lable required">新群主</span>
48
+          <serviceSingle v-if="isShowService" source="chatGroup" title="" width="400px" placeholder="请选择新群主" :reset="reset" :afferent_userId="form.new_owner" @customerDefine="onChangeNewOwner" />
49
+        </div>
50
+
51
+        <div class="form-item">
52
+          <span class="lable required">分配方式</span>
53
+          <el-radio v-model="form.transfer_type" :label="1">立即分配</el-radio>
54
+          <el-radio v-model="form.transfer_type" :label="2">定时分配</el-radio>
55
+        </div>
56
+
57
+        <div v-show="form.transfer_type == 2" class="form-item">
58
+          <span class="lable required">执行分配时间</span>
59
+          <el-date-picker v-model="form.transfer_at" type="datetime" :picker-options="pickerOptions" clearable value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择执行分配时间" size="mini" />
60
+        </div>
61
+      </div>
62
+
63
+      <div class="button_box">
64
+        <button class="button" @click="onClickSave">保存配置</button>
65
+      </div>
66
+    </div>
67
+  </el-drawer>
68
+</template>
69
+
70
+<script>
71
+import serviceSingle from '@/components/assembly/screen/serviceSingle.vue'
72
+import selfCustomerservice from '@/components/assembly/screen/customerService.vue'
73
+import selfChatGroupOptions from '@/components/assembly/screen/chatGroupOptions.vue'
74
+
75
+export default {
76
+  components: {
77
+    serviceSingle,
78
+    selfCustomerservice,
79
+    selfChatGroupOptions,
80
+  },
81
+  props: {
82
+    // 控制弹框是否显示
83
+    value: {
84
+      type: Boolean,
85
+      default: () => false
86
+    },
87
+    config_id: {
88
+      type: String | Number,
89
+      default: () => ''
90
+    },
91
+  },
92
+  data() {
93
+    return {
94
+      pickerOptions: {
95
+        disabledDate (time) {
96
+          return time < Date.now() - (24*60*60*1000);
97
+        }
98
+      },
99
+      loading: false,
100
+      reset: false,
101
+      isShowService: true,
102
+      form: {
103
+        title: '',
104
+        type: 1,
105
+        filter_type: 1,
106
+        new_owner: '',
107
+        old_owners: [],
108
+        chat_ids: [],
109
+        transfer_type: 1,
110
+        transfer_at: '',
111
+      },
112
+    }
113
+  },
114
+  computed: {
115
+    isEdit() {
116
+      return !!this.config_id
117
+    },
118
+    title() {
119
+      return this.isEdit ? `编辑客户群分配配置` : `新建客户群分配配置`
120
+    },
121
+    visible: {
122
+      get() {
123
+        return this.value
124
+      },
125
+      set(isShow) {
126
+        this.$emit('input', isShow)
127
+      },
128
+    },
129
+
130
+  },
131
+  watch: {
132
+    async visible(isShow) {
133
+      if (isShow) {
134
+        this.isEdit && this.handleGetDetail()
135
+      } else {
136
+        this.handleClearForm()
137
+      }
138
+    },
139
+  },
140
+  methods: {
141
+    handleCloseDrawer() {
142
+      this.visible = false
143
+    },
144
+    // 获取详情
145
+    async handleGetDetail() {
146
+      this.isShowService = false
147
+      await this.$nextTick()
148
+      // const { title, user_id, old_owners } = this.configInfo
149
+      // this.form.title = title
150
+      // this.form.new_owner = new_owner
151
+      // this.form.old_owners = old_owners.split(',')
152
+      this.isShowService = true
153
+    },
154
+    async onClickSave() {
155
+      try {
156
+        // 表单校验
157
+        await this.handleFormValidate()
158
+        // 提交表单
159
+        this.handleSubmitForm()
160
+      } catch (error) {
161
+        console.log(error)
162
+      }
163
+    },
164
+    handleFormValidate() {
165
+      const { title, new_owner, old_owners } = this.form
166
+      return new Promise((resolve, reject) => {
167
+        if (!title) {
168
+          this.$message.warning('请输入配置标题')
169
+          reject('表单校验未通过')
170
+        } else if (!new_owner) {
171
+          this.$message.warning('请选择新群主')
172
+          reject('表单校验未通过')
173
+        } else if (!old_owners || !old_owners.length) {
174
+          this.$message.warning('请选择群主')
175
+          reject('表单校验未通过')
176
+        } else {
177
+          resolve('表单校验通过')
178
+        }
179
+      })
180
+    },
181
+    async handleSubmitForm() {
182
+      try {
183
+        this.loading = true
184
+        const url = `${this.URL.BASEURL}${this.URL.customerShare_setConfig}`
185
+        const params = {
186
+          title: this.form.title,
187
+          new_owner: this.form.new_owner,
188
+          old_owners: this.form.old_owners.join(',')
189
+        }
190
+        const { data: res = {} } = await this.$axios.post(url, params)
191
+        if (res && res.errno == 0) {
192
+          this.$message.success('保存成功')
193
+          this.handleClearForm()
194
+          this.$emit('confirm', { isEdit: this.isEdit })
195
+        } else if (res.errno != 4002) {
196
+          this.$message.warning(res.err || '保存失败')
197
+        }
198
+      } catch (error) {
199
+        console.log(error)
200
+      } finally {
201
+        this.loading = false
202
+      }
203
+    },
204
+    async handleClearForm() {
205
+      this.reset = !this.reset
206
+      this.form.title = ''
207
+      this.form.new_owner = ''
208
+      this.form.old_owners = []
209
+      this.isShowService = false
210
+      await this.$nextTick()
211
+      this.isShowService = true
212
+    },
213
+    onChangeNewOwner(val) {
214
+      console.log('onChangeNewOwner => ', val)
215
+      this.form.new_owner = val ? val.user_id : ''
216
+    },
217
+    onChangeOldOwners(val) {
218
+      console.log('onChangeOldOwners => val ', val)
219
+      this.form.old_owners = val
220
+    },
221
+    // 监听"选择群聊"变化
222
+    onChangeGlobalChatGroup(selectedChat) {
223
+      console.log('onChangeGlobalChatGroup => selectedChat ', selectedChat)
224
+    },
225
+  },
226
+};
227
+</script>
228
+
229
+<style lang="scss" scoped>
230
+.content-wrap {
231
+  .form-wrap {
232
+    padding: 30px;
233
+    .form-item {
234
+      display: flex;
235
+      align-items: center;
236
+      margin-top: 40px;
237
+      &.flex-start {
238
+        align-items: flex-start;
239
+      }
240
+      &:first-child {
241
+        margin-top: 0;
242
+      }
243
+      .lable {
244
+        flex-shrink: 0;
245
+        width: 120px;
246
+        color: #333;
247
+        font-size: 14px;
248
+        line-height: 28px;
249
+
250
+        &.required {
251
+          position: relative;
252
+          &::before {
253
+            position: absolute;
254
+            left: -8px;
255
+            top: 0;
256
+            content: "*";
257
+            color: #f56c6c;
258
+          }
259
+        }
260
+      }
261
+      .el-input {
262
+        width: 400px;
263
+        /deep/ &.is-disabled .el-input__inner {
264
+          background-color: #F5F7FA;
265
+          border-color: #DCDFE6;
266
+          color: #606266;
267
+          cursor: not-allowed;
268
+        }
269
+      }
270
+      .el-select {
271
+        width: 260px;
272
+      }
273
+      .account-wrap {
274
+        .account-item-wrap {
275
+          margin-top: 35px;
276
+          &:first-child {
277
+            margin-top: 0;
278
+          }
279
+          .corp-wrap {
280
+            display: flex;
281
+            align-items: center;
282
+            .user-select {
283
+              margin-left: 10px;
284
+            }
285
+            .icon-btn {
286
+              font-size: 17px;
287
+              cursor: pointer;
288
+              margin-left: 10px;
289
+              &.el-icon-delete {
290
+                color:#f56c6c;
291
+              }
292
+              &.el-icon-circle-plus-outline {
293
+                color: #00b38a;
294
+              }
295
+            }
296
+
297
+          }
298
+          .user-wrap {
299
+            box-sizing: border-box;
300
+            width: 533px;
301
+            background-color: rgb(247, 247, 247);
302
+            margin-top: 10px;
303
+            padding: 10px 10px 0 10px;
304
+            display: flex;
305
+            align-items: center;
306
+            flex-wrap: wrap;
307
+            .user-item-wrap {
308
+              position: relative;
309
+              margin: 0 14px 10px 0;
310
+              .err-icon {
311
+                position: absolute;
312
+                top: -5px;
313
+                right: -5px;
314
+                color: #c1c1c1;
315
+                font-size: 15px;
316
+                font-weight: 500;
317
+              }
318
+            }
319
+          }
320
+        }
321
+      }
322
+    }
323
+  }
324
+  .button_box {
325
+    margin: 100px 134px;
326
+    display: flex;
327
+    align-items: center;
328
+  }
329
+
330
+  .button {
331
+    width: 178px;
332
+    height: 40px;
333
+    background: #00b38a;
334
+    border-radius: 5px;
335
+    color: #ffffff;
336
+    font-size: 14px;
337
+    border: none;
338
+    margin-right: 10px;
339
+  }
340
+}
341
+</style>

+ 268 - 0
project/src/components/groupTransfer/index.vue

@@ -0,0 +1,268 @@
1
+<template>
2
+  <div v-loading="loading" class="customerShare-wrap">
3
+    <div class="screenBox">
4
+      <div class="filter-wrap">
5
+        <!-- 标题 -->
6
+        <selfInputV2 v-model="filter.keyword" label_name="标题" placeholder="请输入" :labelWidth="true" @change="onChangeKeyword" style="width: auto;" />
7
+        <!-- 新群主 -->
8
+        <serviceSingle title="新群主" placeholder="请选择" source="chatGroup" @customerDefine="onChangeNewOwner" />
9
+        <!-- 状态 -->
10
+        <selfChannelV2 v-model="filter.status" type="groupTransfer" title="状态" :labelWidth="true" @change="onChangeStatus" />
11
+      </div>
12
+      <el-button type="primary" size="mini" @click="onClickCreateBtn">客户群分配</el-button>
13
+    </div>
14
+    <el-table :height="height" :data="list" tooltip-effect="dark" style="width: 100%;margin-top:10px">
15
+      <el-table-column label="配置标题" prop="title" min-width="200" align="center" fixed="left" />
16
+      <el-table-column label="创建人" align="center" min-width="140">
17
+        <template slot-scope="{ row }">
18
+          <div class="customerServiceTagBox">
19
+            <div class="customerServiceTag"><i class="el-icon-user-solid" /> {{ row.create_user }}</div>
20
+          </div>
21
+        </template>
22
+      </el-table-column>
23
+      <el-table-column label="转移类型" align="center" min-width="140">
24
+        <template slot-scope="{ row }">
25
+          <span>{{ row.type == 1 ? '在职迁移' : '离职迁移' }}</span>
26
+        </template>
27
+      </el-table-column>
28
+      <el-table-column label="筛选方式" align="center" min-width="140">
29
+        <template slot-scope="{ row }">
30
+          <span>{{ row.filter_type == 1 ? '按群聊' : '按群主' }}</span>
31
+        </template>
32
+      </el-table-column>
33
+      <el-table-column label="新群主" prop="new_owner_name" min-width="200" align="center" />
34
+      <el-table-column label="状态" align="center" min-width="120">
35
+        <template slot-scope="{ row }">
36
+          <span :class="[handleGetStatusDesc(row.status).cls]">{{ handleGetStatusDesc(row.status).label }}</span>
37
+        </template>
38
+      </el-table-column>
39
+      <el-table-column label="操作" min-width="160" align="center" fixed="right">
40
+        <template slot-scope="{ row }">
41
+          <span v-if="row.status == 0" class="btn c-007AFF" @click="onClickStatus(row, 1)">启用</span>
42
+          <span v-if="row.status == 1" class="btn c-FF604D" @click="onClickStatus(row, 0)">禁用</span>
43
+          <span class="btn c-00b38a" @click="onClickEditBtn(row)">编辑</span>
44
+        </template>
45
+      </el-table-column>
46
+    </el-table>
47
+    <div class="pagination" v-show="pagination.total > 0">
48
+      <el-pagination background :current-page="pagination.page" @current-change="handleCurrentChange" layout="prev, pager, next" :page-count="Number(pagination.pages)" />
49
+    </div>
50
+
51
+    <!-- S 创建配置 -->
52
+    <configDrawer
53
+      v-model="configDrawerVisible"
54
+      :configInfo="currentConfigInfo"
55
+      @confirm="onConfirmConfig"
56
+      @cancel="onCancelConfig"
57
+    />
58
+    <!-- E 创建配置 -->
59
+  </div>
60
+</template>
61
+
62
+<script>
63
+import selfInputV2 from '@/components/assembly/screen/inputV2.vue'
64
+import selfChannelV2 from '@/components/assembly/screen/channelV2.vue'
65
+import serviceSingle from '@/components/assembly/screen/serviceSingle.vue'
66
+import configDrawer from './configDrawer.vue'
67
+const groupTransferStatusMap = new Map([
68
+  [0, {label: '禁用', cls: 'c-F03F5C'}],
69
+  [1, {label: '待分配', cls: 'c-FFB055'}],
70
+  [2, {label: '分配中', cls: 'c-448AFF'}],
71
+  [3, {label: '分配完成', cls: 'c-58BCA6'}],
72
+  [4, {label: '无目标客户群', cls: 'c-F03F5C'}],
73
+  [5, {label: '分配失败', cls: 'c-F03F5C'}],
74
+])
75
+
76
+export default {
77
+  name: 'groupTransfer',
78
+  components: {
79
+    selfInputV2,
80
+    selfChannelV2,
81
+    serviceSingle,
82
+    configDrawer,
83
+  },
84
+  data () {
85
+    return {
86
+      height: '',
87
+      loading: false,
88
+      pagination: {
89
+        page: 1,
90
+        page_size: 20,
91
+        pages: 0,
92
+        total: 0,
93
+      },
94
+      filter: {
95
+        keyword: '',
96
+        new_owner: '',
97
+        status: '',
98
+      },
99
+      list: [],
100
+
101
+      configDrawerVisible: false,
102
+      currentConfigInfo: {},
103
+    }
104
+  },
105
+  created () {
106
+    this.height = document.documentElement.clientHeight - 200
107
+    this.handleGetList()
108
+  },
109
+  methods: {
110
+    handleGetStatusDesc(status) {
111
+      return groupTransferStatusMap.get(Number(status)) || {}
112
+    },
113
+    // 获取列表数据
114
+    async handleGetList() {
115
+      try {
116
+        this.loading = true
117
+        const url = `${this.URL.BASEURL}${this.URL.groupTransfer_configList}`
118
+        const params = {
119
+          keyword: this.filter.keyword,
120
+          new_owner: this.filter.new_owner,
121
+          status: this.filter.status,
122
+          page: this.pagination.page,
123
+          page_size: this.pagination.page_size,
124
+        }
125
+        const { data: res = {} } = await this.$axios.get(url, { params })
126
+        if (res && res.errno == 0 && Array.isArray(res.rst.data)) {
127
+          this.list = res.rst.data;
128
+          this.pagination.total = res.rst.pageInfo.total;
129
+          this.pagination.pages = res.rst.pageInfo.pages;
130
+        } else if (res.errno != 4002) {
131
+          this.$message.warning(res.err)
132
+          this.list = [];
133
+          this.pagination.total = 0;
134
+          this.pagination.pages = 0;
135
+        }
136
+      } catch (error) {
137
+        console.log(error)
138
+        this.list = [];
139
+        this.pagination.total = 0;
140
+        this.pagination.pages = 0;
141
+      } finally {
142
+        this.loading = false
143
+      }
144
+    },
145
+    onChangeKeyword(val) {
146
+      this.filter.keyword = val
147
+      this.pagination.page = 1
148
+      this.handleGetList()
149
+    },
150
+    onChangeNewOwner(val) {
151
+      console.log('val => ', val)
152
+      this.filter.new_owner = val ? val.user_id : ''
153
+      this.pagination.page = 1
154
+      this.handleGetList()
155
+    },
156
+    onChangeStatus(val) {
157
+      this.filter.status = val
158
+      this.pagination.page = 1
159
+      this.handleGetList()
160
+    },
161
+    // 监听当前页数变化
162
+    handleCurrentChange(currentPage) {
163
+      this.pagination.page = currentPage
164
+      this.handleGetList()
165
+    },
166
+    // 监听点击切换"状态"
167
+    async onClickStatus({title, config_id}, actionCode) {
168
+      try {
169
+        await this.$confirm(`确定${actionCode == 1 ? '启用' : '禁用'}【${title}】?`, '提示', {
170
+          confirmButtonText: '确定',
171
+          cancelButtonText: '取消',
172
+          type: 'warning'
173
+        })
174
+        this.handleChangeStatus(config_id, actionCode)
175
+      } catch (error) {
176
+        console.log(error)
177
+      }
178
+    },
179
+    // 执行“切换状态”
180
+    async handleChangeStatus(config_id, actionCode) {
181
+      try {
182
+        this.loading = true
183
+        const url = `${this.URL.BASEURL}${this.URL.groupTransfer_changeStatus}`
184
+        const params = {
185
+          config_id,
186
+          status: actionCode,
187
+        }
188
+        const { data: res = {} } = await this.$axios.get(url, { params })
189
+        if (res && res.errno == 0) {
190
+          this.$message.success('操作成功')
191
+          this.handleGetList()
192
+        } else if (res.errno != 4002) {
193
+          this.$message.warning(res.err)
194
+        }
195
+      } catch (error) {
196
+        console.log(error)
197
+      } finally {
198
+        this.loading = false
199
+      }
200
+    },
201
+
202
+    // 监听点击“新建配置”
203
+    onClickCreateBtn() {
204
+      this.currentConfigInfo = {}
205
+      this.configDrawerVisible = true
206
+    },
207
+    onClickEditBtn(row) {
208
+      if (row.status != 1) return this.$message.warning('当前状态不可编辑')
209
+      this.currentConfigInfo = {...row}
210
+      this.configDrawerVisible = true
211
+    },
212
+    onConfirmConfig({ isEdit }) {
213
+      this.configDrawerVisible = false
214
+      !isEdit && (this.pagination.page = 1)
215
+      this.handleGetList()
216
+    },
217
+    onCancelConfig() {
218
+      this.configDrawerVisible = false
219
+    },
220
+  },
221
+}
222
+</script>
223
+
224
+<style lang="scss" scoped>
225
+@import "@/style/list.scss";
226
+.customerShare-wrap {
227
+  /deep/ .el-switch.is-disabled .el-switch__core {
228
+    cursor: pointer;
229
+  }
230
+  /deep/ .el-switch.is-disabled {
231
+    opacity: 1;
232
+  }
233
+  .screenBox {
234
+    background: #fff;
235
+    padding: 5px 20px;
236
+    display: flex;
237
+    justify-content: space-between;
238
+    align-items: center;
239
+    .filter-wrap {
240
+      flex: 1;
241
+      display: flex;
242
+      flex-wrap: wrap;
243
+    }
244
+  }
245
+  .btn {
246
+    cursor: pointer;
247
+    margin-right: 4px;
248
+    &:last-child {
249
+      margin-right: 0;
250
+    }
251
+  }
252
+  .lableBox_dad {
253
+    display: flex;
254
+    align-items: center;
255
+    justify-content: center;
256
+    flex-wrap: wrap;
257
+
258
+    .lableBox {
259
+      font-size: 12px;
260
+      background-color: #f0f0f0;
261
+      padding: 4px 8px;
262
+      margin-top: 5px;
263
+      margin-right: 4px;
264
+      border-radius: 4px;
265
+    }
266
+  }
267
+}
268
+</style>

+ 11 - 0
project/src/router/allRouter.js

@@ -115,6 +115,7 @@ const license = () => import(/* webpackChunkName: 'license' */ '@/components/lic
115 115
 const customerWarn = () => import(/* webpackChunkName: 'customerWarn' */ '@/components/customerWarn/list.vue')
116 116
 
117 117
 const InviteIntoGroup = () => import(/* webpackChunkName: 'InviteIntoGroup' */ '@/components/manage/InviteIntoGroup/index.vue')
118
+const groupTransfer = () => import(/* webpackChunkName: 'groupTransfer' */ '@/components/groupTransfer/index.vue')
118 119
 
119 120
 // name与菜单配置的页面路由一致
120 121
 // meta下isData:true为数据看板,否则为助手
@@ -1025,6 +1026,16 @@ export var allRouter = [
1025 1026
           title: '安卓工具配置'
1026 1027
         }
1027 1028
       },
1029
+      {
1030
+        path: 'groupTransfer',
1031
+        name: 'groupTransfer',
1032
+        component: groupTransfer,
1033
+        meta: {
1034
+          keepAlive: false,
1035
+          isLogin: true,
1036
+          title: '客户群分配'
1037
+        }
1038
+      },
1028 1039
     ]
1029 1040
   }
1030 1041
 ]