Browse Source

feat: 客户意见反馈

zhengxy 1 year ago
parent
commit
5fc334052d

+ 5 - 0
project/package-lock.json

@@ -12250,6 +12250,11 @@
12250 12250
         "vue": "^2.5.2"
12251 12251
       }
12252 12252
     },
12253
+    "vue-lazyload": {
12254
+      "version": "1.3.5",
12255
+      "resolved": "https://registry.npmjs.org/vue-lazyload/-/vue-lazyload-1.3.5.tgz",
12256
+      "integrity": "sha512-SCO/LWgCCbjaregHO4wg2buzITBdPBZRlIS104vERGpT88uxXsK26veuzZpgGAXMR8WpkaR+JDqz80OedpaLiA=="
12257
+    },
12253 12258
     "vue-loader": {
12254 12259
       "version": "13.7.3",
12255 12260
       "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-13.7.3.tgz",

+ 1 - 0
project/package.json

@@ -28,6 +28,7 @@
28 28
     "vue": "^2.5.2",
29 29
     "vue-axios": "^3.2.5",
30 30
     "vue-clipboard2": "^0.3.3",
31
+    "vue-lazyload": "^1.3.5",
31 32
     "vue-masked-input": "^0.5.2",
32 33
     "vue-monoplasty-slide-verify": "^1.1.3",
33 34
     "vue-progress-path": "0.0.2",

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

@@ -483,6 +483,8 @@ var api = {
483 483
   customerShare_changeStatus: '/api/customerShare/changeStatus', // 客户共享 - 配置状态
484 484
   customerShare_setConfig: '/api/customerShare/setConfig', // 客户共享 - 创建配置
485 485
 
486
+  feedback_list: '/api/sys/userOpinionFeedbackList', // 客户意见反馈
487
+
486 488
 };
487 489
 
488 490
 export { api };

+ 148 - 0
project/src/components/feedback/detailDialog.vue

@@ -0,0 +1,148 @@
1
+<template>
2
+  <el-dialog
3
+    :visible.sync="dialogVisible"
4
+    :before-close="handleCancel"
5
+    class="detail-dialog"
6
+    :title="title"
7
+    width="550px"
8
+  >
9
+    <div class="form-wrap" v-loading="loading">
10
+      <!-- 预警人员类型 -->
11
+      <div class="form-item">
12
+        <span class="lable">客户:</span>
13
+        <div class="customer-wrap">
14
+          <img v-lazy="detail.customer_avatar" />
15
+          <span>{{ detail.customer_name }}</span>
16
+        </div>
17
+      </div>
18
+      <div class="form-item">
19
+        <span class="lable">反馈类型:</span>
20
+        <span>{{ detail.type_title }}</span>
21
+      </div>
22
+      <div class="form-item flex-align-start">
23
+        <span class="lable">反馈内容:</span>
24
+        <span class="content-wrap">{{ detail.content }}</span>
25
+      </div>
26
+      <div class="form-item flex-align-start">
27
+        <span class="lable">图片证据:</span>
28
+        <div class="image-wrap" v-if="detail.attachments && detail.attachments.length">
29
+          <el-image
30
+            class="image"
31
+            v-for="imageUrl in detail.attachments"
32
+            v-lazy="imageUrl"
33
+            :src="imageUrl"
34
+            :preview-src-list="detail.attachments">
35
+          </el-image>
36
+        </div>
37
+        <div v-else>无</div>
38
+      </div>
39
+    </div>
40
+    <div slot="footer" class="dialog-footer">
41
+      <el-button size="mini" type="primary" @click="handleCancel">关 闭</el-button>
42
+    </div>
43
+  </el-dialog>
44
+</template>
45
+
46
+<script>
47
+export default {
48
+  props: {
49
+    // 控制弹框是否显示
50
+    dialogVisible: {
51
+      type: Boolean,
52
+      default: () => false
53
+    },
54
+    detail: {
55
+      type: Object,
56
+      default: () => ({})
57
+    }
58
+  },
59
+  data() {
60
+    return {
61
+      loading: false,
62
+    }
63
+  },
64
+  computed: {
65
+    title() {
66
+      return `反馈内容`
67
+    },
68
+  },
69
+  watch: {
70
+    dialogVisible(isShow) {
71
+      if (isShow) {
72
+        // do sth.
73
+      }
74
+    },
75
+  },
76
+  methods: {
77
+    handleCancel() {
78
+      this.$emit('close')
79
+    },
80
+    // 清空弹框表单数据
81
+    handleClearFormData() {
82
+      // this.form.app_id = ''
83
+    },
84
+  },
85
+};
86
+</script>
87
+
88
+<style lang="scss" scoped>
89
+.detail-dialog {
90
+  .form-wrap {
91
+    padding-right: 20px;
92
+    .form-item {
93
+      display: flex;
94
+      align-items: center;
95
+      margin-top: 20px;
96
+      &.flex-align-start {
97
+        align-items: flex-start;
98
+      }
99
+      &:first-child {
100
+        margin-top: 0;
101
+      }
102
+      .lable {
103
+        width: 80px;
104
+        font-weight: 500;
105
+        flex-shrink: 0;
106
+        text-align: right;
107
+        margin-right: 10px;
108
+
109
+        &.required {
110
+          &::before {
111
+            position: relative;
112
+            right: 2px;
113
+            content: "*";
114
+            color: #f56c6c;
115
+          }
116
+        }
117
+      }
118
+      .customer-wrap {
119
+        display: flex;
120
+        align-items: center;
121
+        img {
122
+          border-radius: 50%;
123
+          width: 40px;
124
+          height: 40px;
125
+          margin-right: 6px;
126
+        }
127
+      }
128
+      .content-wrap {
129
+        line-height: 20px;
130
+        margin-top: -2px;
131
+      }
132
+      .image-wrap {
133
+        display: flex;
134
+        flex-wrap: wrap;
135
+        width: 300px;
136
+        .image {
137
+          width: 80px;
138
+          height: 80px;
139
+          margin: 0 4px 4px 0;
140
+        }
141
+      }
142
+    }
143
+  }
144
+  .dialog-footer {
145
+    text-align: center;
146
+  }
147
+}
148
+</style>

+ 175 - 0
project/src/components/feedback/getUserLinkDialog.vue

@@ -0,0 +1,175 @@
1
+<template>
2
+  <el-dialog
3
+    :visible.sync="dialogVisible"
4
+    :before-close="handleCancel"
5
+    class="userLink-dialog"
6
+    :title="title"
7
+    width="750px"
8
+    :close-on-click-modal="false"
9
+  >
10
+
11
+    <div class="form-wrap" v-loading="loading">
12
+      <div class="form-item">
13
+        <span class="lable required">客服:</span>
14
+        <customerServiceCorpV2 ref="customerServiceCorpV2" title="" placeholder="请选择客服" width="200px" @customerDefine="onChangeCorpUserList" />
15
+        <span class="tips">注:选择客服后自动生成反馈链接</span>
16
+      </div>
17
+    </div>
18
+
19
+    <el-table v-if="list && list.length" max-height="500" :data="list" tooltip-effect="dark" style="margin-top:10px;width: 100%;">
20
+      <el-table-column label="客服" prop="name" min-width="140" align="center" fixed="left" show-overflow-tooltip />
21
+      <el-table-column label="企微主体" prop="corp_name" min-width="140" align="center" show-overflow-tooltip />
22
+      <el-table-column label="反馈链接" prop="link" min-width="300" align="center" />
23
+      <el-table-column label="操作" fixed="right" min-width="100" align="center">
24
+        <template slot-scope="{ row }">
25
+          <span class="btn c-00b38a" @click="onClickCopy(row)">复制链接</span>
26
+        </template>
27
+      </el-table-column>
28
+    </el-table>
29
+
30
+    <div slot="footer" class="dialog-footer">
31
+      <!-- <el-button size="mini" type="primary" @click="handleCancel">关 闭</el-button> -->
32
+    </div>
33
+  </el-dialog>
34
+</template>
35
+
36
+<script>
37
+import customerServiceCorpV2 from '@/components/assembly/screen/customerServiceCorpV2.vue'
38
+
39
+export default {
40
+  components: {
41
+    customerServiceCorpV2,
42
+  },
43
+  props: {
44
+    // 控制弹框是否显示
45
+    dialogVisible: {
46
+      type: Boolean,
47
+      default: () => false
48
+    },
49
+  },
50
+  data() {
51
+    return {
52
+      loading: false,
53
+      list: [],
54
+      userList: [],
55
+    }
56
+  },
57
+  computed: {
58
+    title() {
59
+      return `意见反馈链接`
60
+    },
61
+  },
62
+  watch: {
63
+    dialogVisible(isShow) {
64
+      if (isShow) {
65
+        // do sth.
66
+      }
67
+    },
68
+  },
69
+  methods: {
70
+    handleCancel() {
71
+      this.$emit('close')
72
+    },
73
+    // 清空弹框表单数据
74
+    handleClearFormData() {
75
+      // this.form.app_id = ''
76
+    },
77
+
78
+    onChangeCorpUserList({ res_format_1, res_format_2 }) {
79
+      console.log('res_format_1 => ', res_format_1)
80
+      console.log('res_format_2 => ', res_format_2)
81
+      if (res_format_1 && res_format_1.length) {
82
+        this.userList = [...res_format_1]
83
+        this.handleGetUserLink()
84
+      } else {
85
+        this.userList = []
86
+      }
87
+    },
88
+    onClickGetLink() {
89
+      if (!this.userList || !this.userList.length) {
90
+        this.$message.warning('请选择客服(可多选)')
91
+        return false
92
+      }
93
+      this.handleGetUserLink()
94
+    },
95
+    handleGetUserLink() {
96
+      if (!this.userList.length) {
97
+        this.list = []
98
+        return
99
+      }
100
+
101
+      const currentHost = window.location.host;
102
+      this.list = this.userList.map(user => ({
103
+        name: user.name,
104
+        corp_name: user.corp_name,
105
+        link: `${currentHost}/playlet/qwh5/dist/index.html#/feedback?corpid=${user.corpid}&user_id=${user.user_id}`
106
+      }))
107
+    },
108
+    async onClickCopy(row) {
109
+      try {
110
+        await this.$copyText(row.link)
111
+        this.$message.success('复制成功')
112
+      } catch (error) {
113
+        this.$message.error('复制失败,请重试')
114
+      }
115
+    },
116
+  },
117
+};
118
+</script>
119
+
120
+<style lang="scss" scoped>
121
+.userLink-dialog {
122
+  /deep/ .el-dialog__body {
123
+    padding-top: 10px;
124
+    min-height: 150px;
125
+  }
126
+  .form-wrap {
127
+    padding-right: 20px;
128
+    .form-item {
129
+      display: flex;
130
+      align-items: center;
131
+      margin-top: 20px;
132
+      &.flex-align-start {
133
+        align-items: flex-start;
134
+      }
135
+      &:first-child {
136
+        margin-top: 0;
137
+      }
138
+      .lable {
139
+        width: 80px;
140
+        font-weight: 500;
141
+        flex-shrink: 0;
142
+        text-align: right;
143
+        margin-right: 10px;
144
+
145
+        &.required {
146
+          &::before {
147
+            position: relative;
148
+            right: 2px;
149
+            content: "*";
150
+            color: #f56c6c;
151
+          }
152
+        }
153
+      }
154
+      .el-select {
155
+        width: 430px;
156
+      }
157
+      .tips {
158
+        margin-left: 10px;
159
+        font-size: 12px;
160
+        color: #666;
161
+      }
162
+    }
163
+  }
164
+  .dialog-footer {
165
+    text-align: center;
166
+  }
167
+  .btn {
168
+    cursor: pointer;
169
+    margin-right: 4px;
170
+    &:last-child {
171
+      margin-right: 0;
172
+    }
173
+  }
174
+}
175
+</style>

+ 201 - 0
project/src/components/feedback/index.vue

@@ -0,0 +1,201 @@
1
+<template>
2
+  <div v-loading="loading" class="feedback-wrap">
3
+    <div class="self-hint">
4
+      <i class="el-icon-message-solid" />
5
+      <div>
6
+        <p>意见反馈使用说明1mock</p>
7
+        <p>意见反馈使用说明22mock</p>
8
+        <p>意见反馈使用说明333mock</p>
9
+      </div>
10
+    </div>
11
+    <div class="screenBox">
12
+      <div class="filter-wrap">
13
+        <!-- 客服 -->
14
+        <customerServiceCorpV2 title="客服" @customerDefine="onChangeMultiCorpUsers" />
15
+      </div>
16
+      <el-button type="primary" size="mini" @click="onClickGetUserLink">获取反馈链接</el-button>
17
+    </div>
18
+    <el-table :height="height" :data="list" tooltip-effect="dark" style="width: 100%;margin-top:10px">
19
+      <el-table-column label="客服" prop="user_name" min-width="160" align="center" fixed="left" />
20
+      <el-table-column label="企微主体" prop="corp_name" min-width="140" align="center" show-overflow-tooltip />
21
+      <el-table-column label="客户" prop="customer_name" min-width="200" align="center">
22
+        <template slot-scope="{ row }">
23
+          <div class="customer-wrap">
24
+            <!-- <img v-lazy="row.customer_avatar" /> -->
25
+            <div class="name">{{ row.customer_name }}</div>
26
+          </div>
27
+        </template>
28
+      </el-table-column>
29
+      <el-table-column label="反馈类型" prop="type_title" min-width="200" align="center" />
30
+      <el-table-column label="反馈内容" prop="content" min-width="120" align="center" show-overflow-tooltip />
31
+      <el-table-column label="反馈时间" prop="create_time" min-width="160" align="center" show-overflow-tooltip />
32
+      <el-table-column label="操作" min-width="160" align="center" fixed="right">
33
+        <template slot-scope="{ row }">
34
+          <span class="btn c-00b38a" @click="onClickDetail(row)">查看详情</span>
35
+        </template>
36
+      </el-table-column>
37
+    </el-table>
38
+    <div class="pagination" v-show="pagination.total > 0">
39
+      <el-pagination background :current-page="pagination.page" @current-change="handleCurrentChange" layout="prev, pager, next" :page-count="Number(pagination.pages)" />
40
+    </div>
41
+
42
+    <!-- S 查看反馈详情 -->
43
+    <getUserLinkDialog
44
+      :dialogVisible="getUserLinkDialogVisible"
45
+      @close="onCloseUserLink"
46
+    />
47
+    <!-- E 查看反馈详情 -->
48
+
49
+    <!-- S 查看反馈详情 -->
50
+    <detailDialog
51
+      :dialogVisible="detailDialogVisible"
52
+      :detail="currentDetail"
53
+      @close="onCloseDeail"
54
+    />
55
+    <!-- E 查看反馈详情 -->
56
+  </div>
57
+</template>
58
+
59
+<script>
60
+import customerServiceCorpV2 from '@/components/assembly/screen/customerServiceCorpV2.vue'
61
+import getUserLinkDialog from './getUserLinkDialog.vue'
62
+import detailDialog from './detailDialog.vue'
63
+
64
+export default {
65
+  name: 'feedback',
66
+  components: {
67
+    customerServiceCorpV2,
68
+    detailDialog,
69
+    getUserLinkDialog,
70
+  },
71
+  data () {
72
+    return {
73
+      sys_group_id: this.$cookie.getCookie('isSuperManage') == 1 ? sessionStorage.getItem('company_session_defaultCorp_level_1').toString() : '',
74
+      height: '',
75
+      loading: false,
76
+      pagination: {
77
+        page: 1,
78
+        page_size: 20,
79
+        pages: 0,
80
+        total: 0,
81
+      },
82
+      filter: {
83
+        user_id: [],
84
+      },
85
+      list: [],
86
+
87
+      detailDialogVisible: false,
88
+      currentDetail: {},
89
+
90
+      getUserLinkDialogVisible: false,
91
+    }
92
+  },
93
+  created () {
94
+    this.height = document.documentElement.clientHeight - 200
95
+    this.handleGetList()
96
+  },
97
+  methods: {
98
+    // 获取列表数据
99
+    async handleGetList() {
100
+      try {
101
+        this.loading = true
102
+        const url = `${this.URL.BASEURL}${this.URL.feedback_list}`
103
+        const params = {
104
+          sys_group_id: this.sys_group_id,
105
+          user_id: this.filter.user_id,
106
+          page: this.pagination.page,
107
+          page_size: this.pagination.page_size,
108
+        }
109
+        const { data: res = {} } = await this.$axios.post(url, params)
110
+        if (res && res.errno == 0 && Array.isArray(res.rst.data)) {
111
+          this.list = res.rst.data;
112
+          this.pagination.total = res.rst.pageInfo.total;
113
+          this.pagination.pages = res.rst.pageInfo.pages;
114
+        } else if (res.errno != 4002) {
115
+          this.$message.warning(res.err)
116
+          this.list = [];
117
+          this.pagination.total = 0;
118
+          this.pagination.pages = 0;
119
+        }
120
+      } catch (error) {
121
+        console.log(error)
122
+        this.list = [];
123
+        this.pagination.total = 0;
124
+        this.pagination.pages = 0;
125
+      } finally {
126
+        this.loading = false
127
+      }
128
+    },
129
+    // 监听当前页数变化
130
+    handleCurrentChange(currentPage) {
131
+      this.pagination.page = currentPage
132
+      this.handleGetList()
133
+    },
134
+    //监听客服变化
135
+    onChangeMultiCorpUsers({ res_format_1 }) {
136
+      if (res_format_1 && res_format_1.length) {
137
+        this.filter.user_id = res_format_1.map(u => ({
138
+          user_id: u.user_id,
139
+          corpid: u.corpid,
140
+        }))
141
+      } else {
142
+        this.filter.user_id = []
143
+      }
144
+      this.pagination.page = 1
145
+      this.handleGetList()
146
+    },
147
+
148
+    onClickGetUserLink() {
149
+      this.getUserLinkDialogVisible = true
150
+    },
151
+    onCloseUserLink() {
152
+      this.getUserLinkDialogVisible = false
153
+    },
154
+
155
+    onClickDetail(row) {
156
+      this.currentDetail = { ...row }
157
+      this.detailDialogVisible = true
158
+    },
159
+    onCloseDeail() {
160
+      this.currentDetail = {}
161
+      this.detailDialogVisible = false
162
+    },
163
+  },
164
+}
165
+</script>
166
+
167
+<style lang="scss" scoped>
168
+@import "@/style/list.scss";
169
+.feedback-wrap {
170
+  .screenBox {
171
+    background: #fff;
172
+    padding: 5px 20px;
173
+    display: flex;
174
+    justify-content: space-between;
175
+    align-items: center;
176
+    .filter-wrap {
177
+      flex: 1;
178
+      display: flex;
179
+      flex-wrap: wrap;
180
+    }
181
+  }
182
+  .btn {
183
+    cursor: pointer;
184
+    margin-right: 4px;
185
+    &:last-child {
186
+      margin-right: 0;
187
+    }
188
+  }
189
+  .customer-wrap {
190
+    display: flex;
191
+    align-items: center;
192
+    justify-content: center;
193
+    img {
194
+      border-radius: 50%;
195
+      width: 40px;
196
+      height: 40px;
197
+      margin-right: 4px;
198
+    }
199
+  }
200
+}
201
+</style>

+ 1 - 1
project/src/components/license/errListDialog.vue

@@ -28,7 +28,7 @@ export default {
28 28
       default: () => false
29 29
     },
30 30
     list: {
31
-      type: [],
31
+      type: Array,
32 32
       default: () => []
33 33
     }
34 34
   },

+ 3 - 0
project/src/main.js

@@ -34,6 +34,9 @@ Vue.use(VueClipboard)
34 34
 import instructions from '@/components/assembly/instructions.vue'
35 35
 Vue.component("instructions", instructions)
36 36
 
37
+import VueLazyload from 'vue-lazyload'
38
+Vue.use(VueLazyload)
39
+
37 40
 Vue.prototype.$getDay = getDay;
38 41
 Vue.prototype.$timeStamp_to_date = timeStamp_to_date;
39 42
 Vue.prototype.$judgePhone = judgePhone;

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

@@ -38,6 +38,7 @@ const permissionManage = () => import(/* webpackChunkName: 'permissionManage' */
38 38
 const roleManage = () => import(/* webpackChunkName: 'roleManage' */ '@/components/manage/roleManage.vue')
39 39
 const menuManage = () => import(/* webpackChunkName: 'menuManage' */ '@/components/manage/menuManage.vue')
40 40
 const systemMsg = () => import(/* webpackChunkName: 'systemMsg' */ '@/components/manage/systemMsg.vue')
41
+const feedback = () => import(/* webpackChunkName: 'feedback' */ '@/components/feedback/index.vue')
41 42
 const orderPercent = () => import(/* webpackChunkName: 'orderPercent' */ '@/components/manage/orderPercent/index.vue')
42 43
 const thePublic = () => import(/* webpackChunkName: 'thePublic' */ '@/components/dataBoard/thePublic.vue')
43 44
 const playletData = () => import(/* webpackChunkName: 'playletData' */ '@/components/dataBoard/playletData.vue')
@@ -666,6 +667,16 @@ export var allRouter = [
666 667
         }
667 668
       },
668 669
       {
670
+        path: 'feedback',
671
+        name: 'feedback',
672
+        component: feedback,
673
+        meta: {
674
+          keepAlive: false,
675
+          isLogin: true,
676
+          title: '意见反馈',
677
+        }
678
+      },
679
+      {
669 680
         path: 'orderPercent',
670 681
         name: 'orderPercent',
671 682
         component: orderPercent,