Browse Source

feat: 客服许可续期

zhengxy 1 year ago
parent
commit
11469a5f05

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

@@ -257,6 +257,15 @@ export default {
257 257
         { key: 2, val: '其他类型支付' },
258 258
       ]
259 259
       this.placeholderVal = '支付类型'
260
+    } else if (this.type == 'licenseTaskStatus') { // 客服许可任务状态
261
+      this.options = [
262
+        { key: -1, val: '已取消' },
263
+        { key: 0, val: '未执行' },
264
+        { key: 1, val: '执行中' },
265
+        { key: 2, val: '已执行' },
266
+        { key: 3, val: '已完成' },
267
+        { key: 4, val: '执行失败' },
268
+      ]
260 269
     } else {
261 270
       this.init()
262 271
     }

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

@@ -237,6 +237,15 @@ export default {
237 237
         { key: 2, val: '其他类型支付' },
238 238
       ]
239 239
       this.placeholderVal = '支付类型'
240
+    } else if (this.type == 'licenseTaskStatus') { // 客服许可任务状态
241
+      this.options = [
242
+        { key: -1, val: '已取消' },
243
+        { key: 0, val: '未执行' },
244
+        { key: 1, val: '执行中' },
245
+        { key: 2, val: '已执行' },
246
+        { key: 3, val: '已完成' },
247
+        { key: 4, val: '执行失败' },
248
+      ]
240 249
     } else {
241 250
       this.init()
242 251
     }

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

@@ -500,6 +500,15 @@ export default {
500 500
           { key: 2, val: '其他类型支付' },
501 501
         ]
502 502
         this.placeholderVal = '支付类型'
503
+      } else if (this.type == 'licenseTaskStatus') { // 客服许可任务状态
504
+        this.options = [
505
+          { key: -1, val: '已取消' },
506
+          { key: 0, val: '未执行' },
507
+          { key: 1, val: '执行中' },
508
+          { key: 2, val: '已执行' },
509
+          { key: 3, val: '已完成' },
510
+          { key: 4, val: '执行失败' },
511
+        ]
503 512
       } else {
504 513
         this.init()
505 514
       }

+ 314 - 0
project/src/components/license/dialog/operatorGroupDialog.vue

@@ -0,0 +1,314 @@
1
+<template>
2
+  <el-dialog
3
+    :visible.sync="dialogVisible"
4
+    :before-close="handleCancel"
5
+    class="operatorGroup-dialog"
6
+    :title="title"
7
+    width="590px"
8
+    :close-on-click-modal="false"
9
+  >
10
+    <div class="form-wrap" v-loading="loading">
11
+      <div class="form-item">
12
+        <span class="lable required">名称:</span>
13
+        <el-input v-model="form.group_name" size="small" placeholder="请输入名称" clearable />
14
+      </div>
15
+      <div class="form-item">
16
+        <span class="lable required">运营:</span>
17
+        <selfChannel v-if="isShowOperatorOptions" title="" type="circleCreate" placeholder="请选择" :labelWidth="true" width="440px" :reset="reset" :afferent_params="{ type: 1, operator_id: form.operator_id || '' }" :afferent_value="form.operator_id" @channelDefine="onChangeOperatorId" />
18
+      </div>
19
+      <div class="form-item" style="margin-top:10px;">
20
+        <span class="lable required">成员:</span>
21
+        <customerServiceCorpV2 ref="customerServiceCorpV2" title="" width="440px" :afferent_users="form.afferent_corp_user_list" @customerDefine="onChangeCorpUserList" />
22
+      </div>
23
+    </div>
24
+    <div slot="footer" class="dialog-footer">
25
+      <el-button size="mini" @click="handleCancel" :disabled="loading">取 消</el-button>
26
+      <el-button size="mini" type="primary" @click="handleConfirm" :disabled="loading">确 定</el-button>
27
+    </div>
28
+  </el-dialog>
29
+</template>
30
+
31
+<script>
32
+import _lodash from 'lodash'
33
+import selfChannel from '@/components/assembly/screen/channel.vue'
34
+import customerServiceCorpV2 from '@/components/assembly/screen/customerServiceCorpV2.vue'
35
+
36
+export default {
37
+  name: "operatorGroupDialog",
38
+  components: {
39
+    selfChannel,
40
+    customerServiceCorpV2,
41
+  },
42
+  props: {
43
+    // 控制弹框是否显示
44
+    dialogVisible: {
45
+      type: Boolean,
46
+      default: () => false
47
+    },
48
+    // 运营组ID
49
+    groupId: {
50
+      type: [String, Number],
51
+      default: () => ''
52
+    },
53
+  },
54
+  data() {
55
+    return {
56
+      loading: false,
57
+      reset: false,
58
+      isShowOperatorOptions: false,
59
+      form: {
60
+        group_name: '', // 名称
61
+        operator_id: '', // 运营
62
+        afferent_corp_user_list: [], // 多主体运营组成员 [{ user_id: 888, corpid: 666 }]
63
+        corp_user_list: [], // 多主体运营组成员
64
+      }
65
+    }
66
+  },
67
+  computed: {
68
+    isEdit() {
69
+      return !!this.groupId
70
+    },
71
+    title() {
72
+      return `${this.isEdit ? '编辑' : '新建'}运营组`
73
+    },
74
+  },
75
+  watch: {
76
+    dialogVisible(isShow) {
77
+      if (isShow) {
78
+        // 获取表单数据
79
+        this.handleGetFormData()
80
+      }
81
+    },
82
+  },
83
+  methods: {
84
+    onChangeCorpUserList({ res_format_1 }) {
85
+      if (res_format_1 && res_format_1.length) {
86
+        this.form.corp_user_list = res_format_1.map(r => ({
87
+          corpid: r.corpid,
88
+          user_id: r.user_id,
89
+        }))
90
+      } else {
91
+        this.form.corp_user_list = []
92
+      }
93
+    },
94
+    // 监听"运营"筛选变化
95
+    onChangeOperatorId(val) {
96
+      this.form.operator_id = val
97
+    },
98
+    // 确定
99
+    async handleConfirm() {
100
+      try {
101
+        // 表单校验
102
+        await this.handleFormValidate()
103
+        const url = `${this.URL.BASEURL}${this.URL.operatorGroup_setConfig}`
104
+        const params = this.handleGetParams()
105
+        this.loading = true
106
+        const { data: res = {} } = await this.$axios.post(url, params)
107
+        if (res && res.errno == 0) {
108
+          this.$message.success('操作成功')
109
+          this.handleClearFormData()
110
+          this.$emit('confirm', { isEdit: this.isEdit })
111
+        } else if (res.errno != 4002) {
112
+          this.$message.warning(res.err || '操作失败')
113
+        }
114
+      } catch (error) {
115
+        console.log('error => ', error)
116
+      } finally {
117
+        this.loading = false
118
+      }
119
+    },
120
+    // 取消
121
+    handleCancel() {
122
+      this.handleClearFormData()
123
+      this.$emit('cancel')
124
+    },
125
+    // 执行表单校验
126
+    handleFormValidate() {
127
+      return new Promise((resolve, reject) => {
128
+        const { group_name, operator_id, corp_user_list } = this.form
129
+        if (group_name === '') {
130
+          this.$message.warning('请输入名称')
131
+          reject('表单校验未通过')
132
+        } else if (!operator_id) {
133
+          this.$message.warning('请选择运营')
134
+          reject('表单校验未通过')
135
+        } else if (!corp_user_list || !corp_user_list.length) {
136
+          this.$message.warning('请选择成员')
137
+          reject('表单校验未通过')
138
+        } else {
139
+          resolve('表单校验通过')
140
+        }
141
+      })
142
+    },
143
+    // 整理请求参数
144
+    handleGetParams() {
145
+      const params = {
146
+        group_name: this.form.group_name,
147
+        operator_id: this.form.operator_id,
148
+        corp_user_list: JSON.stringify(this.form.corp_user_list),
149
+      }
150
+
151
+      if (this.isEdit) {
152
+        params.group_id = this.groupId
153
+      }
154
+
155
+      return {...params}
156
+    },
157
+    // 获取表单数据
158
+    async handleGetFormData() {
159
+      this.handleClearFormData()
160
+      await this.$nextTick()
161
+      if (this.isEdit) { // 编辑 => 获取运营组详情 => 回显表单数据
162
+        this.handleGetGroupDetail()
163
+      } else { // 新建
164
+        this.handleShowOperatorOpt()
165
+      }
166
+    },
167
+    // 获取运营组详情
168
+    async handleGetGroupDetail() {
169
+      try {
170
+        const url = `${this.URL.BASEURL}${this.URL.operatorGroup_groupDetail}`
171
+        const params = {
172
+          group_id: this.groupId,
173
+        }
174
+        this.loading = true
175
+        const { data: res = {} } = await this.$axios.get(url, { params })
176
+        if (res && res.errno == 0) {
177
+          const detailInfo = res.rst
178
+          this.form.group_name = detailInfo.group_name
179
+          this.form.operator_id = detailInfo.operator_id
180
+
181
+          this.handleShowOperatorOpt() // 回显
182
+
183
+          if (detailInfo.corp_user_list && detailInfo.corp_user_list.length) {
184
+            const userListRes = []
185
+            detailInfo.corp_user_list.forEach(c => {
186
+              if (c && c.user_list && Array.isArray(c.user_list)) {
187
+                c.user_list.forEach(userId => {
188
+                  userListRes.push({
189
+                    user_id: userId,
190
+                    corpid: c.app_id,
191
+                  })
192
+                })
193
+              }
194
+            })
195
+            this.form.afferent_corp_user_list = [...userListRes]
196
+            this.form.corp_user_list = _lodash.cloneDeep(this.form.afferent_corp_user_list)
197
+          }
198
+
199
+        } else if (res.errno != 4002) {
200
+          this.$message.warning(res.err || '操作失败')
201
+        }
202
+      } catch (error) {
203
+        console.log('error => ', error)
204
+      } finally {
205
+        this.loading = false
206
+      }
207
+    },
208
+    // 清空弹框表单数据
209
+    handleClearFormData() {
210
+      this.form.group_name = ''
211
+      this.form.operator_id = ''
212
+      this.form.afferent_corp_user_list = []
213
+      this.form.corp_user_list = []
214
+      this.$refs.customerServiceCorpV2 && this.$refs.customerServiceCorpV2.handleCloseAllOpen()
215
+      this.reset = !this.reset
216
+      this.isShowOperatorOptions = false
217
+    },
218
+
219
+    // 显示运营选项
220
+    handleShowOperatorOpt() {
221
+      this.isShowOperatorOptions = true
222
+    },
223
+  },
224
+};
225
+</script>
226
+
227
+<style lang="scss" scoped>
228
+.operatorGroup-dialog {
229
+  /deep/ .el-dialog__body {
230
+    max-height: 380px;
231
+    overflow-y: auto;
232
+  }
233
+  .form-wrap {
234
+    padding-right: 20px;
235
+    .form-item {
236
+      display: flex;
237
+      align-items: center;
238
+      margin-top: 20px;
239
+      &.flex-align-start {
240
+        align-items: flex-start;
241
+      }
242
+      &:first-child {
243
+        margin-top: 0;
244
+      }
245
+      .lable {
246
+        width: 80px;
247
+        font-weight: 500;
248
+        flex-shrink: 0;
249
+        text-align: right;
250
+        margin-right: 10px;
251
+
252
+        &.required {
253
+          &::before {
254
+            position: relative;
255
+            right: 2px;
256
+            content: "*";
257
+            color: #f56c6c;
258
+          }
259
+        }
260
+      }
261
+      .el-select {
262
+        width: 430px;
263
+      }
264
+      .rules-wrap {
265
+        box-sizing: border-box;
266
+        background-color: #F2F2F2;
267
+        padding: 14px;
268
+        width: 430px;
269
+        .rules-item {
270
+          display: flex;
271
+          align-items: center;
272
+          flex-shrink: 0;
273
+          margin-bottom: 10px;
274
+          .el-input {
275
+            width: 70px;
276
+            margin-right: 6px;
277
+            /deep/ .el-input__inner {
278
+              padding: 0 24px 0 8px;
279
+            }
280
+          }
281
+          .el-select {
282
+            width: 100px;
283
+            margin-right: 6px;
284
+          }
285
+          .text {
286
+            margin-right: 6px;
287
+            font-size: 13px;
288
+          }
289
+          .el-icon-error {
290
+            color: #F56C6C;
291
+            font-size: 14px;
292
+            cursor: pointer;
293
+          }
294
+        }
295
+        .add-wrap {
296
+          margin-top: 16px;
297
+          display: flex;
298
+          align-items: center;
299
+          color: #00B38A;
300
+          font-size: 13px;
301
+          cursor: pointer;
302
+          i {
303
+            font-weight: 600;
304
+            margin-right: 2px;
305
+          }
306
+        }
307
+      }
308
+    }
309
+  }
310
+  .dialog-footer {
311
+    text-align: center;
312
+  }
313
+}
314
+</style>

+ 251 - 0
project/src/components/license/index.vue

@@ -0,0 +1,251 @@
1
+<template>
2
+  <div v-loading="loading" class="operatorGroup-wrap">
3
+    <div class="screenBox">
4
+      <div class="filter-wrap">
5
+        <!-- 任务名称 -->
6
+        <selfInputV2 style=""  :labelWidth="true" v-model="filter.title" label_name="任务名称" placeholder="请输入" @change="onChangeTitle" />
7
+        <!-- 任务状态 -->
8
+        <selfChannelV2 style="margin-right: 10px;" v-model="filter.status" type='licenseTaskStatus' title="任务状态" :labelWidth="true" @change="onChangeStatus" />
9
+      </div>
10
+      <el-button type="primary" size="mini" @click="onClickCreateBtn">新建运营组</el-button>
11
+    </div>
12
+    <el-table :height="height" :data="list" tooltip-effect="dark" style="width: 100%;margin-top:10px">
13
+      <el-table-column label="名称" prop="group_name" min-width="200" align="center" fixed="left">
14
+        <template slot-scope="{ row }">
15
+          <div> {{ row.group_name || '-' }} </div>
16
+        </template>
17
+      </el-table-column>
18
+      <el-table-column label="运营" prop="operator_name" min-width="200" align="center">
19
+        <template slot-scope="{ row }">
20
+          <div class="customerServiceTagBox">
21
+            <div class="customerServiceTag"><i class="el-icon-user-solid" /> {{ row.operator_name }}</div>
22
+          </div>
23
+        </template>
24
+      </el-table-column>
25
+      <el-table-column label="成员" prop="corp_user_list" min-width="400" align="center">
26
+        <template slot-scope="{ row }">
27
+          <div class="lableBox_dad">
28
+            <template v-if="row.corp_user_list && row.corp_user_list">
29
+              <div v-for="c in row.corp_user_list" :key="c.corpid+c.user_id" class="lableBox">
30
+                {{ c.user_info }}
31
+              </div>
32
+            </template>
33
+            <template v-else>-</template>
34
+          </div>
35
+        </template>
36
+      </el-table-column>
37
+      <el-table-column label="操作" min-width="160" align="center" fixed="right">
38
+        <template slot-scope="{ row }">
39
+          <template>
40
+            <span v-if="row.enable == enableTypes.ENABLE" class="btn c-FF604D" @click="onClickSetStatus({ group_id: row.group_id, enable: enableTypes.DISABLE })">禁用</span>
41
+            <span v-else-if="row.enable == enableTypes.DISABLE" class="btn c-007AFF" @click="onClickSetStatus({ group_id: row.group_id, enable: enableTypes.ENABLE })">启用</span>
42
+          </template>
43
+          <span class="btn c-00b38a" @click="onClickEditBtn(row.group_id)">编辑</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
+      </el-pagination>
50
+    </div>
51
+
52
+    <!-- S 新建运营组/编辑运营组 -->
53
+    <operatorGroupDialog
54
+      :dialogVisible="operatorGroupDialogVisible"
55
+      :groupId="currentOperatorGroupId"
56
+      @confirm="onConfirmOperatorGroup"
57
+      @cancel="onCancelOperator"
58
+    />
59
+    <!-- E 新建运营组/编辑运营组 -->
60
+  </div>
61
+</template>
62
+
63
+<script>
64
+import operatorGroupDialog from './dialog/operatorGroupDialog.vue'
65
+import { enableTypes } from './staticTools'
66
+import selfChannelV2 from '@/components/assembly/screen/channelV2.vue'
67
+import selfInputV2 from '@/components/assembly/screen/inputV2.vue'
68
+
69
+export default {
70
+  name: 'operatorGroup',
71
+  components: {
72
+    operatorGroupDialog,
73
+    selfChannelV2,
74
+    selfInputV2,
75
+  },
76
+  data () {
77
+    return {
78
+      enableTypes: Object.freeze(enableTypes),
79
+      height: '',
80
+      loading: false,
81
+      pagination: {
82
+        page: 1,
83
+        page_size: 20,
84
+        pages: 0,
85
+        total: 0,
86
+      },
87
+      filter: {
88
+        title: '',
89
+        status: '',
90
+      },
91
+      list: [],
92
+
93
+      operatorGroupDialogVisible: false, // 控制是否显示“新建运营组”、“编辑运营组”弹框
94
+      currentOperatorGroupId: '', // 当前运营组规则id
95
+    }
96
+  },
97
+  created () {
98
+    this.height = document.documentElement.clientHeight - 200
99
+    this.handleGetList()
100
+  },
101
+  methods: {
102
+    // 获取列表数据
103
+    async handleGetList() {
104
+      try {
105
+        this.loading = true
106
+        const url = `${this.URL.BASEURL}${this.URL.operatorGroup_groupList}`
107
+        const params = {
108
+          page: this.pagination.page,
109
+          page_size: this.pagination.page_size,
110
+          operator_id: this.filter.operator_id,
111
+        }
112
+        const { data: res = {} } = await this.$axios.get(url, { params })
113
+        if (res && res.errno == 0 && Array.isArray(res.rst.data)) {
114
+          this.list = res.rst.data;
115
+          this.pagination.total = res.rst.pageInfo.total;
116
+          this.pagination.pages = res.rst.pageInfo.pages;
117
+        } else if (res.errno != 4002) {
118
+          this.$message.warning(res.err)
119
+          this.list = [];
120
+          this.pagination.total = 0;
121
+          this.pagination.pages = 0;
122
+        }
123
+      } catch (error) {
124
+        console.log(error)
125
+        this.list = [];
126
+        this.pagination.total = 0;
127
+        this.pagination.pages = 0;
128
+      } finally {
129
+        this.loading = false
130
+      }
131
+    },
132
+    // 监听"任务标题"变化
133
+    onChangeTitle(val) {
134
+      this.filter.title = val
135
+      this.pagination.page = 1
136
+      this.handleGetList()
137
+    },
138
+    // 监听"任务状态"变化
139
+    onChangeStatus(val) {
140
+      this.filter.status = (val || val === 0) ? val : ''
141
+      this.pagination.page = 1
142
+      this.handleGetList()
143
+    },
144
+    // 监听当前页数变化
145
+    handleCurrentChange(currentPage) {
146
+      this.pagination.page = currentPage
147
+      this.handleGetList()
148
+    },
149
+    // 监听点击“禁用/启用”
150
+    async onClickSetStatus({ group_id, enable }) {
151
+      try {
152
+        await this.$confirm(`确定${enable == enableTypes.ENABLE ? '启用' : '禁用'}当前运营组吗?`, '提示', {
153
+          confirmButtonText: '确定',
154
+          cancelButtonText: '取消',
155
+          type: 'warning'
156
+        })
157
+        this.handleSetStatus({ group_id, enable })
158
+      } catch (error) {
159
+        console.log('error => ', error)
160
+      }
161
+    },
162
+    // 执行设置“禁用/启用”
163
+    async handleSetStatus({ group_id, enable }) {
164
+      try {
165
+        this.loading = true
166
+        const url = `${this.URL.BASEURL}${this.URL.operatorGroup_changeStatus}`
167
+        const params = {
168
+          group_id,
169
+          status: enable,
170
+        }
171
+        const { data: res = {} } = await this.$axios.get(url, { params })
172
+        if (res && res.errno == 0) {
173
+          this.$message.success('操作成功')
174
+          this.handleGetList()
175
+        } else if (res.errno != 4002) {
176
+          this.$message.warning(res.err)
177
+        }
178
+      } catch (error) {
179
+        console.log(error)
180
+      } finally {
181
+        this.loading = false
182
+      }
183
+    },
184
+
185
+    // S 新建运营组、编辑运营组
186
+    // 监听点击“新建运营组”
187
+    onClickCreateBtn() {
188
+      this.currentOperatorGroupId = ''
189
+      this.operatorGroupDialogVisible = true
190
+    },
191
+    // 监听点击“编辑运营组”
192
+    onClickEditBtn(id) {
193
+      this.currentOperatorGroupId = id
194
+      this.operatorGroupDialogVisible = true
195
+    },
196
+    // 监听新建运营组 => 确定
197
+    onConfirmOperatorGroup({ isEdit }) {
198
+      this.operatorGroupDialogVisible = false
199
+      if (!isEdit) {
200
+        this.pagination.page = 1
201
+      }
202
+      this.handleGetList()
203
+    },
204
+    // 监听新建运营组 => 取消
205
+    onCancelOperator() {
206
+      this.operatorGroupDialogVisible = false
207
+    },
208
+    // E 新建运营组、编辑运营组
209
+  },
210
+}
211
+</script>
212
+
213
+<style lang="scss" scoped>
214
+@import "@/style/list.scss";
215
+.operatorGroup-wrap {
216
+  .screenBox {
217
+    background: #fff;
218
+    padding: 5px 20px;
219
+    display: flex;
220
+    justify-content: space-between;
221
+    align-items: center;
222
+    .filter-wrap {
223
+      flex: 1;
224
+      display: flex;
225
+      flex-wrap: wrap;
226
+    }
227
+  }
228
+  .btn {
229
+    cursor: pointer;
230
+    margin-right: 4px;
231
+    &:last-child {
232
+      margin-right: 0;
233
+    }
234
+  }
235
+  .lableBox_dad {
236
+    display: flex;
237
+    align-items: center;
238
+    justify-content: center;
239
+    flex-wrap: wrap;
240
+
241
+    .lableBox {
242
+      font-size: 12px;
243
+      background-color: #f0f0f0;
244
+      padding: 4px 8px;
245
+      margin-top: 5px;
246
+      margin-right: 4px;
247
+      border-radius: 4px;
248
+    }
249
+  }
250
+}
251
+</style>

+ 19 - 0
project/src/components/license/staticTools.js

@@ -0,0 +1,19 @@
1
+const enableTypes = {
2
+  'ENABLE': 1, // 启用
3
+  'DISABLE': 0, // 禁用
4
+}
5
+const contrastOptions = [
6
+  { label: '小于等于', value: 1 },
7
+  { label: '大于等于', value: 2 },
8
+]
9
+
10
+const noticeTypes = { // 预警类型
11
+  'USER': 1, // 预警人
12
+  'GROUP': 2, // 预警组
13
+}
14
+
15
+export {
16
+  enableTypes,
17
+  contrastOptions,
18
+noticeTypes,
19
+}

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

@@ -106,6 +106,8 @@ const phaseConfig = () => import(/* webpackChunkName: 'phaseConfig' */ '@/compon
106 106
 const customerAlerts = () => import(/* webpackChunkName: 'customerAlerts' */ '@/components/customerAlerts/index.vue')
107 107
 // 设置管理 - 运营组
108 108
 const operatorGroup = () => import(/* webpackChunkName: 'operatorGroup' */ '@/components/operatorGroup/index.vue')
109
+// 设置管理 - 客服许可续期
110
+const license = () => import(/* webpackChunkName: 'license' */ '@/components/license/index.vue')
109 111
 
110 112
 const InviteIntoGroup = () => import(/* webpackChunkName: 'InviteIntoGroup' */ '@/components/manage/InviteIntoGroup/index.vue')
111 113
 
@@ -560,6 +562,16 @@ export var allRouter = [
560 562
         }
561 563
       },
562 564
       {
565
+        path: 'license',
566
+        name: 'license',
567
+        component: license,
568
+        meta: {
569
+          keepAlive: false,
570
+          isLogin: true,
571
+          title: '客服许可续期'
572
+        }
573
+      },
574
+      {
563 575
         path: 'authorityManage',
564 576
         name: 'authorityManage',
565 577
         component: authorityManage,