Browse Source

feat: 企微H5 - 推送详情

zhengxy 2 years ago
parent
commit
db184e41b7
3 changed files with 423 additions and 0 deletions
  1. 6 0
      qwh5/src/router/index.ts
  2. 17 0
      qwh5/src/utils/common.ts
  3. 400 0
      qwh5/src/views/userSopH5.vue

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

@@ -4,6 +4,7 @@ import radar from '../views/radar.vue'
4 4
 import radarH5 from '../views/radarH5.vue'
5 5
 import quickwordH5 from '../views/quickwordH5.vue'
6 6
 import groupCodeH5 from '../views/groupCodeH5.vue' // 渠道群活码H5
7
+import userSopH5 from '../views/userSopH5.vue' // 个人SOP H5
7 8
 
8 9
 
9 10
 const routes: Array<RouteRecordRaw> = [
@@ -33,6 +34,11 @@ const routes: Array<RouteRecordRaw> = [
33 34
     component: groupCodeH5
34 35
   },
35 36
   {
37
+    path: '/userSopH5',
38
+    name: 'userSopH5',
39
+    component: userSopH5
40
+  },
41
+  {
36 42
     path: '/radarH5',
37 43
     name: 'radarH5',
38 44
     component: radarH5

+ 17 - 0
qwh5/src/utils/common.ts

@@ -20,4 +20,21 @@ export function getQueryString(name?: string) {
20 20
     return theRequest
21 21
   }
22 22
   return theRequest[name] ? theRequest[name] : ''
23
+}
24
+// 判断字符串是否为JSON格式
25
+export function isJSON(str) {
26
+  if (typeof str == 'string') {
27
+    try {
28
+      let obj=JSON.parse(str);
29
+      if(typeof obj == 'object' && obj ){
30
+        return true;
31
+      }else{
32
+        return false;
33
+      }
34
+    } catch(e) {
35
+      return false;
36
+    }
37
+  } else {
38
+    return false
39
+  }
23 40
 }

+ 400 - 0
qwh5/src/views/userSopH5.vue

@@ -0,0 +1,400 @@
1
+<template>
2
+  <div class="user-sop-wrap">
3
+    <div class="tips-1">{{ sopDetail.desc }}</div>
4
+    <div class="title-1">推送详情</div>
5
+    <div class="tips-2">企微助手提醒您给一下客户发送消息</div>
6
+    <div class="title-2">
7
+      <span>推送内容</span>
8
+      <span v-if="sopDetail.msg_list && sopDetail.msg_list.length > 1" class="collapse-btn" @click="onClickCollapse">
9
+        {{ isShowMore ? '收起' : '查看全部' }}
10
+      </span>
11
+    </div>
12
+    <div class="msg-list-wrap">
13
+      <!-- 默认只展示第一个消息 -->
14
+      <template v-if="!isShowMore">
15
+        <div v-if="sopDetail.msg_list && sopDetail.msg_list[0]" class="msg-item-wrap">
16
+          <template v-if="sopDetail.msg_list[0].msg_type==1">
17
+            {{ sopDetail.msg_list[0]?.content?.content || '-' }}
18
+          </template>
19
+          <template v-else-if="sopDetail.msg_list[0].msg_type==2">
20
+            <div class="flex">
21
+              <img :src="sopDetail.msg_list[0]?.content?.oss_url" class="coverImg flex-shrink">
22
+              <div class="title">{{ sopDetail.msg_list[0]?.content?.name || '-' }}</div>
23
+            </div>
24
+          </template>
25
+          <template v-else-if="sopDetail.msg_list[0].msg_type==3">
26
+            <div class="flex">
27
+              <video class="video-wrap flex-shrink" controls :src="sopDetail.msg_list[0]?.content?.oss_url" />
28
+              <div class="title">{{ sopDetail.msg_list[0]?.content?.name || '-' }}</div>
29
+            </div>
30
+          </template>
31
+          <template v-else-if="sopDetail.msg_list[0].msg_type==4">
32
+            <div class="flex">
33
+              <div class="flex-shrink">【文件】:</div>
34
+              <div class="title">{{ sopDetail.msg_list[0]?.content?.name || '-' }}</div>
35
+            </div>
36
+          </template>
37
+          <template v-else-if="sopDetail.msg_list[0].msg_type==5">
38
+            <div class="flex">
39
+              <img :src="sopDetail.msg_list[0]?.content?.imgUrl" class="coverImg flex-shrink">
40
+              <div>
41
+                <div class="title">{{ sopDetail.msg_list[0]?.content?.title || '-' }}</div>
42
+                <div class="desc">{{ sopDetail.msg_list[0]?.content?.desc || '-' }}</div>
43
+              </div>
44
+            </div>
45
+          </template>
46
+          <template v-else-if="sopDetail.msg_list[0].msg_type==6">
47
+            <div class="flex">
48
+              <div class="flex-shrink">【小程序】:</div>
49
+              <div class="title">{{ sopDetail.msg_list[0]?.content?.title || '-' }}</div>
50
+            </div>
51
+          </template>
52
+        </div>
53
+      </template>
54
+      <!-- 展示全部消息 -->
55
+      <template v-else>
56
+        <div class="msg-item-wrap" v-for="(item, idx) in sopDetail.msg_list" :key="idx">
57
+          <template v-if="item.msg_type==1">
58
+            {{ item?.content?.content || '-' }}
59
+          </template>
60
+          <template v-else-if="item.msg_type==2">
61
+            <div class="flex">
62
+              <img :src="item?.content?.oss_url" class="coverImg flex-shrink">
63
+              <div class="title">{{ item?.content?.name || '-' }}</div>
64
+            </div>
65
+          </template>
66
+          <template v-else-if="item.msg_type==3">
67
+            <div class="flex">
68
+              <video class="video-wrap flex-shrink" controls :src="item?.content?.oss_url" />
69
+              <div class="title">{{ item?.content?.name || '-' }}</div>
70
+            </div>
71
+          </template>
72
+          <template v-else-if="item.msg_type==4">
73
+            <div class="flex">
74
+              <div class="flex-shrink">【文件】:</div>
75
+              <div class="title">{{ item?.content?.name || '-' }}</div>
76
+            </div>
77
+          </template>
78
+          <template v-else-if="item.msg_type==5">
79
+            <div class="flex">
80
+              <img :src="item?.content?.imgUrl" class="coverImg flex-shrink">
81
+              <div>
82
+                <div class="title">{{ item?.content?.title || '-' }}</div>
83
+                <div class="desc">{{ item?.content?.desc || '-' }}</div>
84
+              </div>
85
+            </div>
86
+          </template>
87
+          <template v-else-if="item.msg_type==6">
88
+            <div class="flex">
89
+              <div class="flex-shrink">【小程序】:</div>
90
+              <div class="title">{{ item?.content?.title || '-' }}</div>
91
+            </div>
92
+          </template>
93
+        </div>
94
+      </template>
95
+    </div>
96
+    <div class="title-2">选择客户跟进</div>
97
+    <van-list
98
+      v-model:loading="customer.loading"
99
+      :finished="customer.finished"
100
+      finished-text="没有更多了"
101
+      @load="handleGetCustomer"
102
+    >
103
+      <div class="customer-wrap" v-for="(item, idx) in customer.list" :key="idx">
104
+        <div class="lt-wrap">
105
+          <div>
106
+            <img class="avatar" :src="item.avatar" />
107
+          </div>
108
+          <div class="info">
109
+            <div class="name">
110
+              <span>{{ item.name }}</span>
111
+              <span class="source">@{{ item.source }}</span>
112
+            </div>
113
+            <div class="time">
114
+              <span>添加时间:</span>
115
+              <span>{{ item.createtime }}</span>
116
+            </div>
117
+          </div>
118
+        </div>
119
+        <div class="rt-wrap">
120
+          <div class="btn" @click="onClickCustomer(item)">跟进</div>
121
+        </div>
122
+      </div>
123
+    </van-list>
124
+  </div>
125
+</template>
126
+
127
+<script lang="ts" setup>
128
+import { ref, reactive, onMounted, onBeforeMount } from 'vue'
129
+import { Toast } from 'vant';
130
+import { getQueryString, isJSON } from '@/utils/common'
131
+import $axios from '@/utils/axios'
132
+import getWxConfig from '@/utils/getWxConfig';
133
+
134
+interface IRes<T = any> {
135
+  errno: string | number
136
+  rst: T
137
+  [index: string]: any
138
+}
139
+
140
+// 请求参数
141
+const params = reactive({
142
+  corpid: getQueryString('corpid'),
143
+  rule_id: getQueryString('rule_id'),
144
+  user_id: getQueryString('user_id'),
145
+})
146
+
147
+const sopDetail = ref<any>({
148
+  msg_list: []
149
+})
150
+const isShowMore = ref(false)
151
+
152
+const onClickCollapse = () => {
153
+  isShowMore.value = !isShowMore.value
154
+}
155
+
156
+onBeforeMount(() => {//组件挂载之前
157
+  if (getQueryString('againJump')) {
158
+    params.corpid = getQueryString('corpid')
159
+    params.rule_id = getQueryString('rule_id')
160
+    params.user_id = getQueryString('user_id')
161
+    getWxConfig(() => {
162
+      // handleGetSopDetail()
163
+    });
164
+  } else {//获取用户信息
165
+    getWxConfig('', (authInfo) => {
166
+      let redirect_uri = encodeURIComponent(window.location.href + '&againJump=true')
167
+      // let redirect_uri = encodeURIComponent('http://duanju.wenxingshuju.com/playlet/qwh5/dist/index.html#/userSopH5?corpid=wpezvKNwAA9d7LlcuOOAhvlx5ikwJjHg&user_id=woezvKNwAAitJGbknzFNsLkMN656NUjg&rule_id=7' + '&againJump=true')
168
+
169
+
170
+      let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${authInfo.corpid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_base&state=1&agentid=${authInfo.agent_id}#wechat_redirect`;
171
+      let link = document.createElement("a");
172
+      link.href = url;
173
+      document.body.appendChild(link);
174
+      link.click();
175
+      document.body.removeChild(link);
176
+    });
177
+  }
178
+});
179
+
180
+onMounted(() => {
181
+  handleGetSopDetail()
182
+})
183
+
184
+
185
+// 获取SOP规则详情
186
+const handleGetSopDetail = async () => {
187
+  try {
188
+    Toast.loading({
189
+      message: '加载中...',
190
+      duration: 0,
191
+      forbidClick: true,
192
+    })
193
+    const { errno, rst } = await $axios.get('/api/h5/userSop/detail', {
194
+      corpid: params.corpid,
195
+      rule_id: params.rule_id,
196
+    }) as IRes
197
+    if (errno == 0) {
198
+      console.log('rst => ', rst)
199
+      if (rst?.data?.msg_list && Array.isArray(rst.data.msg_list) ) { // 解析JSON类型消息体
200
+        rst.data.msg_list.forEach(msg => {
201
+          if (isJSON(msg.content)) {
202
+            msg.content = JSON.parse(msg.content)
203
+          }
204
+        })
205
+      }
206
+      sopDetail.value = rst.data
207
+    }
208
+  } catch (error) {
209
+    console.log('error =>', error)
210
+  } finally {
211
+    Toast.clear();
212
+  }
213
+}
214
+
215
+
216
+const customer = reactive({
217
+  list: [],
218
+  loading: false,
219
+  finished: false,
220
+  page: 1,
221
+  page_size: 5,
222
+})
223
+// 获取待跟进客户列表
224
+const handleGetCustomer = async () => {
225
+  try {
226
+    Toast.loading({
227
+      message: '加载中...',
228
+      duration: 0,
229
+      forbidClick: true,
230
+    })
231
+    const { errno, rst } = await $axios.get('/api/h5/userSop/customer', {
232
+      corpid: params.corpid,
233
+      rule_id: params.rule_id,
234
+      user_id: params.user_id,
235
+      page: customer.page,
236
+      page_size: customer.page_size,
237
+    }) as IRes
238
+    if (errno == 0) {
239
+      console.log('rst => ', rst)
240
+      if (Array.isArray(rst?.data)) {
241
+        customer.list = [...customer.list, ...rst.data]
242
+        if (customer.page < rst?.pageInfo?.pages) {
243
+          customer.page = customer.page + 1
244
+        } else {
245
+          customer.finished = true
246
+        }
247
+      }
248
+    }
249
+  } catch (error) {
250
+    console.log('error =>', error)
251
+  } finally {
252
+    Toast.clear();
253
+    customer.loading = false
254
+  }
255
+}
256
+
257
+// 监听点击"跟进"客户
258
+const onClickCustomer = (customer) => {
259
+  console.log('onClickCustomer => ', customer)
260
+  wx.invoke('openUserProfile', {
261
+    "type": 2, //1表示该userid是企业成员,2表示该userid是外部联系人
262
+    "userid": customer.external_userid //可以是企业成员,也可以是外部联系人
263
+  }, function(res) {
264
+    console.log('res => ', res)
265
+    if(res.err_msg != "openUserProfile:ok") {
266
+        //错误处理
267
+    }
268
+  });
269
+}
270
+
271
+</script>
272
+
273
+<style lang="scss" scoped>
274
+.user-sop-wrap {
275
+  box-sizing: border-box;
276
+  min-height: 100vh;
277
+  background-color: #FFF;
278
+  padding: 20px;
279
+  .tips-1 {
280
+    font-weight: 600;
281
+    font-size: 14px;
282
+    color: #F59A22;
283
+  }
284
+  .title-1 {
285
+    margin-top: 20px;
286
+    font-size: 16px;
287
+    font-weight: 600;
288
+    color: #333;
289
+  }
290
+  .tips-2 {
291
+    margin-top: 10px;
292
+    font-size: 14px;
293
+    color: #333;
294
+  }
295
+  .title-2 {
296
+    margin-top: 20px;
297
+    font-size: 14px;
298
+    color: #333;
299
+    font-weight: 600;
300
+    display: flex;
301
+    justify-content: space-between;
302
+    .collapse-btn {
303
+      color: #32B38C;
304
+      font-size: 12px;
305
+      font-weight: 400;
306
+    }
307
+  }
308
+  .msg-list-wrap {
309
+    margin-top: 10px;
310
+    .msg-item-wrap {
311
+      background-color: #f8f8f8;
312
+      padding: 13px 14px;
313
+      margin-top: 4px;
314
+      color: #333;
315
+      word-wrap: break-word;
316
+      font-size: 14px;
317
+      .flex{
318
+        display:flex;
319
+        align-items:center;
320
+        .flex-shrink {
321
+          flex-shrink: 0;
322
+        }
323
+      }
324
+      .coverImg{
325
+        width:100px;
326
+        margin-right:10px;
327
+      }
328
+      .title{
329
+        color: #444;
330
+        font-size: 14px;
331
+        word-wrap:break-word;
332
+      }
333
+      .video-wrap {
334
+        width: 100px;
335
+        height: 150px;
336
+        margin-right: 10px;
337
+        background-color: #666;
338
+      }
339
+      .desc{
340
+        color: #999;
341
+        margin-top: 4px;
342
+        font-size: 12px;
343
+        word-wrap:break-word;
344
+      }
345
+    }
346
+  }
347
+
348
+  .customer-wrap {
349
+    display: flex;
350
+    align-items: center;
351
+    justify-content: space-between;
352
+    padding: 10px;
353
+    margin-top: 10px;
354
+    background-color: #F8F8F8;
355
+    .lt-wrap {
356
+      flex: 1;
357
+      flex-shrink: 0;
358
+      display: flex;
359
+      align-items: center;
360
+      .avatar {
361
+        width: 50px;
362
+        height: 50px;
363
+      }
364
+      .info {
365
+        margin-left: 4px;
366
+        font-size: 14px;
367
+        color: #333;
368
+        .name {
369
+          .source {
370
+            margin-left: 3px;
371
+            color: #00b38a;
372
+          }
373
+        }
374
+
375
+        .time {
376
+          margin-top: 10px;
377
+          font-size: 12px;
378
+          color: #666;
379
+        }
380
+      }
381
+    }
382
+    .rt-wrap {
383
+      flex-shrink: 0;
384
+      .btn {
385
+        box-sizing: border-box;
386
+        border: 2px solid #00b38a;
387
+        width: 56px;
388
+        height: 24px;
389
+        font-size: 14px;
390
+        color: #00b38a;
391
+        display: flex;
392
+        align-items: center;
393
+        justify-content: center;
394
+        background-color: #fff;
395
+      }
396
+    }
397
+  }
398
+
399
+}
400
+</style>