Browse Source

feat: H5 - 客户意见反馈

zhengxy 1 year ago
parent
commit
d28a5c31c8

+ 3 - 0
qwh5/components.d.ts

@@ -17,6 +17,8 @@ declare module '@vue/runtime-core' {
17 17
     Tag: typeof import('./src/components/tag.vue')['default']
18 18
     TestCom: typeof import('./src/components/TestCom.vue')['default']
19 19
     VanActionSheet: typeof import('vant/es')['ActionSheet']
20
+    VanButton: typeof import('vant/es')['Button']
21
+    VanCell: typeof import('vant/es')['Cell']
20 22
     VanCellGroup: typeof import('vant/es')['CellGroup']
21 23
     VanCollapse: typeof import('vant/es')['Collapse']
22 24
     VanCollapseItem: typeof import('vant/es')['CollapseItem']
@@ -30,6 +32,7 @@ declare module '@vue/runtime-core' {
30 32
     VanSearch: typeof import('vant/es')['Search']
31 33
     VanTab: typeof import('vant/es')['Tab']
32 34
     VanTabs: typeof import('vant/es')['Tabs']
35
+    VanUploader: typeof import('vant/es')['Uploader']
33 36
   }
34 37
   export interface ComponentCustomProperties {
35 38
     vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']

+ 16 - 0
qwh5/src/router/index.ts

@@ -74,6 +74,22 @@ const routes: Array<RouteRecordRaw> = [
74 74
       title: '短剧推荐'
75 75
     },
76 76
   },
77
+  {
78
+    path: '/feedback',
79
+    name: 'feedback',
80
+    component: () => import(/*webpackChunkName:"feedback" */ '@/views/feedback.vue'),
81
+    meta: {
82
+      title: '意见反馈'
83
+    },
84
+  },
85
+  {
86
+    path: '/feedback/success',
87
+    name: 'feedbackSuccess',
88
+    component: () => import(/*webpackChunkName:"feedbackSuccess" */ '@/views/feedbackSuccess.vue'),
89
+    meta: {
90
+      title: '意见反馈'
91
+    },
92
+  },
77 93
 ]
78 94
 
79 95
 const router = createRouter({

+ 25 - 0
qwh5/src/utils/axios.ts

@@ -140,6 +140,31 @@ export default {
140 140
     })
141 141
   },
142 142
 
143
+  upload(url: string, data?: object, errToast?: any) {
144
+    return new Promise((resolve, reject) => {
145
+      service({
146
+        method: 'post',
147
+        url,
148
+        data: data,
149
+        headers: {
150
+          'Content-Type': 'multipart/form-data;'
151
+        }
152
+      }).then(res => {
153
+        if (res.data.errno == 0) {
154
+          resolve(res.data)
155
+        } else {
156
+          resolve(res.data)
157
+          if (errToast) {
158
+            Toast(res.data.err)
159
+          }
160
+        }
161
+      }).catch(err => {
162
+        console.log(err.message, 'err');
163
+        reject(err)
164
+      });
165
+    })
166
+  },
167
+
143 168
   get(url: string, data?: object, errToast?: any) {
144 169
     let ksort_data: any = ksort(data);
145 170
     let str = '';

+ 309 - 0
qwh5/src/views/feedback.vue

@@ -0,0 +1,309 @@
1
+<template>
2
+  <div class="feedback-wrap">
3
+    <div v-if="isShowForm" class="form-wrap">
4
+      <div class="imgs-wrap">
5
+        <div class="label">
6
+          <span>图片证据(选填)</span>
7
+          <span class="count">{{ fileList.length }}张/9</span>
8
+        </div>
9
+        <div class="uploader-wrap">
10
+          <van-uploader class="img-item" v-model="fileList" multiple :max-count="9" accept="image/*" :before-read="beforeRead" :after-read="afterRead" />
11
+        </div>
12
+      </div>
13
+      <van-field
14
+        v-model="params.content"
15
+        rows="3"
16
+        autosize
17
+        :label="''"
18
+        type="textarea"
19
+        maxlength="200"
20
+        placeholder="投诉内容(必填)"
21
+        show-word-limit
22
+      />
23
+      <div class="footer">
24
+        <van-button type="success" size="small" @click="onClickSubmit">提交</van-button>
25
+      </div>
26
+    </div>
27
+
28
+    <div v-else class="typeList-wrap">
29
+      <p class="notice">{{ typeList.notice }}</p>
30
+      <van-cell v-for="item in typeList.data" :key="item.type" :title="item.title" is-link size="large" @click="onClickTypeItem(item)" />
31
+    </div>
32
+  </div>
33
+</template>
34
+
35
+<script lang="ts" setup>
36
+import { getQueryString } from '@/utils/common'
37
+import { onBeforeMount, onMounted, reactive, ref } from "vue";
38
+import { Toast, Cell, Uploader, Button } from 'vant';
39
+import $axios from '@/utils/axios'
40
+import { useRouter } from 'vue-router'
41
+const $router = useRouter()
42
+
43
+interface IRes<T = any> {
44
+  errno: string | number
45
+  rst: T
46
+  [index: string]: any
47
+}
48
+
49
+const params = reactive({
50
+  corpid: getQueryString('corpid'),
51
+  user_id: getQueryString('user_id'),
52
+  external_userid: '',
53
+  type: '', // 反馈类型
54
+  content: '', // 反馈内容
55
+  attachments: [], // 反馈图片
56
+})
57
+
58
+
59
+const typeList = ref({})
60
+const isShowForm = ref(false)
61
+
62
+const goUrl = (url) => {
63
+  let link = document.createElement("a");
64
+  link.href = url;
65
+  document.body.appendChild(link);
66
+  link.click();
67
+  document.body.removeChild(link);
68
+}
69
+
70
+const getUserInfo = () => {
71
+  Toast.loading({
72
+    duration: 0,
73
+    message: '加载中...',
74
+    forbidClick: true,
75
+  });
76
+  $axios.get('/api/oauth2/userInfo', {
77
+    corpid: getQueryString('corpid'),
78
+    code: getQueryString('code'),
79
+    state: getQueryString('state')
80
+  }, true).then((res) => {
81
+    Toast.clear()
82
+    params.external_userid = res.rst.external_userid
83
+  }).catch((err) => {
84
+    Toast.clear()
85
+    Toast(err.message)
86
+  })
87
+}
88
+
89
+// 动态修改微信网页标题
90
+const setTitle = function (t) {
91
+  document.title = t;
92
+  var i = document.createElement("iframe");
93
+  i.src = "./favicon.ico";
94
+  i.style.display = "none";
95
+  i.onload = function () {
96
+    setTimeout(function () {
97
+      i.remove();
98
+    }, 9);
99
+  };
100
+  document.body.appendChild(i);
101
+};
102
+const changeTitleClick = () => {
103
+  setTimeout(function () {
104
+    setTitle("意见反馈");
105
+  }, 1);
106
+}
107
+
108
+const handleGetList = async () => {
109
+  try {
110
+    Toast.loading({
111
+      message: '加载中...',
112
+      duration: 0,
113
+      forbidClick: true,
114
+    })
115
+    const { errno, rst, err } = await $axios.get('/api/opinionFeedbackTypeList', {}) as IRes
116
+    if (errno == 0 && rst) {
117
+      typeList.value = {...rst}
118
+    } else {
119
+      Toast({ message: err || '加载失败' })
120
+    }
121
+  } catch (error) {
122
+    console.log('error =>', error)
123
+  } finally {
124
+    Toast.clear();
125
+  }
126
+}
127
+
128
+const onClickTypeItem = (typeItem) => {
129
+  if (typeItem && typeItem.child) {
130
+    typeList.value = {...typeItem.child}
131
+  } else {
132
+    params.type = typeItem.type
133
+    isShowForm.value = true
134
+  }
135
+}
136
+
137
+
138
+const fileList = ref([])
139
+const afterRead = (file) => {
140
+  // 此时可以自行将文件上传至服务器
141
+  if (file.length) { //判断是否选择了多个文件,如果是的话,循环遍历单个文件上传
142
+    file.forEach((f) => {
143
+      handleUploadFile(f);
144
+    });
145
+  } else {
146
+    handleUploadFile(file);
147
+  }
148
+};
149
+const beforeRead = (file) => {
150
+  const maxSize = 1000 * 1024 * 10;
151
+  if (file.size >= maxSize) {
152
+    Toast('图片大小不能超过10M');
153
+    return false
154
+  } else {
155
+    return true
156
+  }
157
+}
158
+const handleUploadFile = (file) => {
159
+    const formData = new FormData();
160
+    formData.append("file", file.file);
161
+    formData.append("corpid", params.corpid);
162
+    formData.append("type", '1');
163
+
164
+    Toast.loading({
165
+      duration: 0, // 持续展示 toast
166
+      forbidClick: true,
167
+      message: '上传中',
168
+    });
169
+
170
+    $axios.upload('/api/uploadFileToOss', formData).then((res) => {
171
+      Toast.clear()
172
+      const { url } = res.rst
173
+      file.url = url
174
+    }).catch((err) => {
175
+      Toast.clear()
176
+      Toast(err.message)
177
+    })
178
+}
179
+
180
+
181
+// 判断当前环境
182
+const isWechat = () => {
183
+  var ua = window.navigator.userAgent.toLowerCase();
184
+  if (ua.match(/micromessenger/i) == 'micromessenger') { // 判断是否在微信浏览器内
185
+    return true;
186
+  } else {
187
+    return false;
188
+  }
189
+}
190
+function closePage() {
191
+  // isWechat是我写的一个判断当前环境是否是微信内置浏览器 的方法
192
+  if (!isWechat()) return // 非微信环境下,不做处理
193
+  setTimeout(function() {
194
+    //安卓手机
195
+    document.addEventListener("WeixinJSBridgeReady", function() {
196
+      WeixinJSBridge.call("closeWindow");
197
+    }, false);
198
+    //ios手机
199
+    WeixinJSBridge.call("closeWindow");
200
+  }, 100);
201
+}
202
+
203
+const onClickSubmit = () => {
204
+  if (!params.content) {
205
+    Toast('请输入投诉内容')
206
+    return false
207
+  }
208
+
209
+  Toast.loading({
210
+    duration: 0,
211
+    message: '加载中...',
212
+    forbidClick: true,
213
+  });
214
+  $axios.post('/api/userOpinionFeedbackAction', {
215
+    corpid: params.corpid,
216
+    user_id: params.user_id,
217
+    external_userid: params.external_userid,
218
+    type: params.type,
219
+    content: params.content,
220
+    attachments: fileList.value && fileList.value.length ? fileList.value.map(f => f.url) : [],
221
+  }).then(res => {
222
+    Toast(JSON.stringify(res))
223
+    if (res && res.errno == 0) {
224
+      isShowForm.value = false
225
+      $router.replace('/feedback/success')
226
+    } else {
227
+      Toast(res.err || '提交失败')
228
+    }
229
+  }).catch((err) => {
230
+    Toast.clear()
231
+    Toast(err.message)
232
+  })
233
+}
234
+
235
+onBeforeMount(() => {
236
+  if (getQueryString('againJump')) {
237
+    getUserInfo()
238
+  } else {
239
+    let redirect_uri = encodeURIComponent(window.location.href + '&againJump=true')
240
+    let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${getQueryString('corpid')}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_base&state=1&agentid=1000012#wechat_redirect`;
241
+    goUrl(url)
242
+  }
243
+});
244
+
245
+onMounted(() => {
246
+  changeTitleClick()
247
+  handleGetList()
248
+})
249
+</script>
250
+
251
+<style lang="scss" scoped>
252
+.feedback-wrap {
253
+  background-color: #f8f8f8;
254
+  min-height: 100vh;
255
+  .form-wrap {
256
+    .imgs-wrap {
257
+      background-color: #fff;
258
+      margin-bottom: 2px;
259
+      .label {
260
+        padding: 20px 14px;
261
+        font-size: 16px;
262
+        color: rgb(50, 50, 51);
263
+        display: flex;
264
+        align-items: center;
265
+        justify-content: space-between;
266
+        .count {
267
+          color: #999;
268
+        }
269
+      }
270
+      .uploader-wrap {
271
+        .img-item {
272
+          padding: 0 0 0 16px;
273
+          :deep(.van-uploader__preview) {
274
+            width: 29vw;
275
+            height: 29vw;
276
+          }
277
+          :deep(.van-uploader__upload) {
278
+            width: 29vw;
279
+            height: 29vw;
280
+          }
281
+          :deep(.van-uploader__preview-image) {
282
+            width: 29vw;
283
+            height: 29vw;
284
+          }
285
+        }
286
+      }
287
+    }
288
+
289
+    .footer {
290
+      padding: 40px;
291
+      display: flex;
292
+      justify-content: center;
293
+      .van-button {
294
+        width: 40vw;
295
+        font-size: 16px;
296
+      }
297
+    }
298
+
299
+  }
300
+  .typeList-wrap {
301
+    .notice {
302
+      padding: 20px 14px 6px;
303
+      font-size: 16px;
304
+      color: #666;
305
+    }
306
+  }
307
+}
308
+
309
+</style>

+ 107 - 0
qwh5/src/views/feedbackSuccess.vue

@@ -0,0 +1,107 @@
1
+<template>
2
+  <div class="feedbackSuccess-wrap">
3
+    <van-icon class="icon-cls" name="checked" />
4
+    <div class="title">投诉已提交</div>
5
+    <div class="tips">微信团队会尽快核实,并通过“微信团队”通知你审核结果。感谢你的支持。</div>
6
+    <van-button class="footer-btn" type="success" size="small" @click="onClickClose">关闭</van-button>
7
+  </div>
8
+</template>
9
+
10
+<script lang="ts" setup>
11
+import { getQueryString } from '@/utils/common'
12
+import { onBeforeMount, onMounted, reactive, ref } from "vue";
13
+import { Toast, Icon, Button } from 'vant';
14
+import $axios from '@/utils/axios'
15
+import { useRouter } from 'vue-router'
16
+const $router = useRouter()
17
+
18
+const onClickClose = () => {
19
+  closePage()
20
+}
21
+
22
+
23
+
24
+
25
+
26
+
27
+// 动态修改微信网页标题
28
+const setTitle = function (t) {
29
+  document.title = t;
30
+  var i = document.createElement("iframe");
31
+  i.src = "./favicon.ico";
32
+  i.style.display = "none";
33
+  i.onload = function () {
34
+    setTimeout(function () {
35
+      i.remove();
36
+    }, 9);
37
+  };
38
+  document.body.appendChild(i);
39
+};
40
+const changeTitleClick = () => {
41
+  setTimeout(function () {
42
+    setTitle("意见反馈");
43
+  }, 1);
44
+}
45
+
46
+// 判断当前环境
47
+const isWechat = () => {
48
+  var ua = window.navigator.userAgent.toLowerCase();
49
+  if (ua.match(/micromessenger/i) == 'micromessenger') { // 判断是否在微信浏览器内
50
+    return true;
51
+  } else {
52
+    return false;
53
+  }
54
+}
55
+function closePage() {
56
+  // isWechat是我写的一个判断当前环境是否是微信内置浏览器 的方法
57
+  if (!isWechat()) return // 非微信环境下,不做处理
58
+  setTimeout(function() {
59
+    //安卓手机
60
+    document.addEventListener("WeixinJSBridgeReady", function() {
61
+      WeixinJSBridge.call("closeWindow");
62
+    }, false);
63
+    //ios手机
64
+    WeixinJSBridge.call("closeWindow");
65
+  }, 100);
66
+}
67
+
68
+onMounted(() => {
69
+  changeTitleClick()
70
+})
71
+</script>
72
+
73
+<style lang="scss" scoped>
74
+.feedbackSuccess-wrap {
75
+  background-color: #f8f8f8;
76
+  min-height: 100vh;
77
+  display: flex;
78
+  flex-direction: column;
79
+  align-items: center;
80
+  padding: 0 40px;
81
+  .icon-cls {
82
+    margin-top: 40px;
83
+    color: #07c160;
84
+    font-size: 50px;
85
+  }
86
+  .title {
87
+    font-size: 18px;
88
+    font-weight: 600;
89
+    color: #333;
90
+    margin-top: 30px;
91
+  }
92
+  .tips {
93
+    font-size: 16px;
94
+    color: #666;
95
+    text-align: center;
96
+    margin-top: 30px;
97
+    line-height: 20px;
98
+  }
99
+  .footer-btn {
100
+    position: fixed;
101
+    bottom: 20vh;
102
+    width: 40vw;
103
+    font-size: 16px;
104
+  }
105
+}
106
+
107
+</style>