clientSop.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>数据查看</title>
  8. <!--引入 element-ui 的样式,-->
  9. <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  10. <!-- 必须先引入vue, 后使用element-ui -->
  11. <script src="./js/vue.js"></script>
  12. <!-- 引入element 的组件库-->
  13. <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  14. <script src="https://unpkg.com/vconsole/dist/vconsole.min.js"></script>
  15. <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  16. <script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
  17. <script>
  18. var vConsole = new window.VConsole();
  19. </script>
  20. </head>
  21. <style>
  22. body {
  23. margin: 0;
  24. padding: 0;
  25. }
  26. .box {
  27. width: 100vw;
  28. height: 100vh;
  29. box-sizing: border-box;
  30. background: #FAFAFA;
  31. }
  32. .page4 {
  33. width: 100vw;
  34. height: 100vh;
  35. box-sizing: border-box;
  36. }
  37. .top_tip {
  38. background: #afdefd;
  39. display: flex;
  40. justify-content: center;
  41. color: #FFF;
  42. font-size: 14px;
  43. padding: 6px 0;
  44. }
  45. .logo {
  46. width: 20px;
  47. height: 20px;
  48. margin-right: 10px;
  49. }
  50. .tag_list {
  51. display: flex;
  52. align-items: center;
  53. padding: 10px 20px;
  54. overflow-x: auto;
  55. }
  56. .content {
  57. padding: 20px;
  58. margin-bottom: 10px;
  59. background: #FFF;
  60. }
  61. .icon_logo {
  62. width: 20px;
  63. vertical-align: bottom;
  64. }
  65. .sop_content {
  66. background: #f1f9ff;
  67. padding: 10px 20px;
  68. border-radius: 10px;
  69. font-size: 14px;
  70. }
  71. .font {
  72. color: #1890ff;
  73. }
  74. .sop_list {
  75. padding: 10px 20px;
  76. }
  77. .sop_time {
  78. font-weight: 600;
  79. }
  80. .dot {
  81. width: 10px;
  82. height: 10px;
  83. color: #1890ff;
  84. }
  85. .sop_item {
  86. background: #FFF;
  87. padding: 20px;
  88. margin: 10px 0;
  89. font-size: 14px;
  90. }
  91. .sop_body {
  92. margin: 10px 0;
  93. }
  94. .sop_c {
  95. background: #FAFAFA;
  96. padding: 10px;
  97. }
  98. .sop_foot {
  99. display: flex;
  100. justify-content: space-between;
  101. align-items: center;
  102. margin: 10px 0;
  103. }
  104. .sop_btn {
  105. background: #1890ff;
  106. color: #FFFFFF;
  107. padding: 5px 20px;
  108. border-radius: 4px;
  109. font-size: 12px;
  110. }
  111. </style>
  112. <body>
  113. <div id="box" class="box">
  114. <!-- 数据查看 -->
  115. <div class="page4">
  116. <div class="top_tip">
  117. <img class="logo" src="./img/wefan.png"></img>
  118. 微分机器人提供技术支持
  119. </div>
  120. <el-tabs v-model="activeName" stretch @tab-click="handleClickTab">
  121. <el-tab-pane label="执行中" name="playing">
  122. <div class="tag_list">
  123. <el-tag style="margin-right: 10px;" :type="item.type" effect="plain" v-for="(item, index) in executeData" :key="index" @click="getSop(item)">{{item.sopName}}</el-tag>
  124. </div>
  125. <div class="content">
  126. <div class="sop_content">
  127. <img class="icon_logo" :src="sopItem.clientAvatar || './img/avatar.png'"></img>
  128. <span>「{{sopItem.clientName}}」在「{{timeFormat(sopItem.triggerTime, 'yyyy-MM-dd hh:mm:ss')}}」</span>
  129. <span class="font" v-if="sopItem.triggerType">添加好友后触发客户SOP任务,</span>
  130. <span class="font" v-else>打上标签后触发客户SOP任务,</span>
  131. <span>快去跟进吧</span>
  132. </div>
  133. </div>
  134. <div class="sop_list" v-for="(item, index) in sopPlans" :key="index">
  135. <div class="sop_time">
  136. <span class="dot">·</span>
  137. {{timeFormat(item.executeTime, 'yyyy-MM-dd')}}
  138. </div>
  139. <div class="sop_item">
  140. <div><span style="color: #1890ff;">{{timeFormat(item.executeTime, 'hh:mm:ss')}}</span>发送</div>
  141. <div class="sop_body" v-if="item.ctOthers.length > 0">
  142. <div v-for="(c, cIndex) in item.ctOthers" :key="cIndex">
  143. <div class="sop_c" v-if="c.matterType === 0">
  144. <span>{{ c.matterText }}</span>
  145. </div>
  146. <div class="sop_c" v-if="c.matterType === 1">
  147. <img style="width: 100px;" :src="c.imgUrl" />
  148. </div>
  149. <div class="sop_c" v-if="c.matterType === 5">
  150. <span>{{ c.linkTitle }}</span>
  151. </div>
  152. <div class="sop_c" v-if="c.matterType === 2">
  153. <span>自定义视频</span>
  154. </div>
  155. <div class="sop_c" v-if="c.matterType === 4">
  156. <span>{{ c.fileName }}</span>
  157. </div>
  158. <div class="sop_foot">
  159. <span style="color: #999;">{{item.isSend ? '已发送' : ''}}</span>
  160. <div class="sop_btn" @click="handleSend(item.id, c)">发送</div>
  161. </div>
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. </el-tab-pane>
  167. <el-tab-pane label="已完成" name="ending">
  168. <div class="tag_list">
  169. <el-tag style="margin-right: 10px;" :type="item.type" effect="plain" v-for="(item, index) in executeData" :key="index"
  170. @click="getSop(item)">{{item.sopName}}</el-tag>
  171. </div>
  172. <div class="content">
  173. <div class="sop_content">
  174. <img class="icon_logo" :src="sopItem.clientAvatar || './img/avatar.png'"></img>
  175. <span>「{{sopItem.clientName}}」在「{{timeFormat(sopItem.triggerTime, 'yyyy-MM-dd hh:mm:ss')}}」</span>
  176. <span class="font" v-if="sopItem.triggerType">添加好友后触发客户SOP任务,</span>
  177. <span class="font" v-else>打上标签后触发客户SOP任务,</span>
  178. <span>快去跟进吧</span>
  179. </div>
  180. </div>
  181. <div class="sop_list" v-for="(item, index) in sopPlans" :key="index">
  182. <div class="sop_time">
  183. <span class="dot">·</span>
  184. {{timeFormat(item.executeTime, 'yyyy-MM-dd')}}
  185. </div>
  186. <div class="sop_item">
  187. <div><span style="color: #1890ff;">{{timeFormat(item.executeTime, 'hh:mm:ss')}}</span>发送</div>
  188. <div class="sop_body" v-if="item.ctOthers.length > 0">
  189. <div v-for="(c, cIndex) in item.ctOthers" :key="cIndex">
  190. <div class="sop_c" v-if="c.matterType === 0">
  191. <span>{{ c.matterText }}</span>
  192. </div>
  193. <div class="sop_c" v-if="c.matterType === 1">
  194. <img style="width: 100px;" :src="c.imgUrl" />
  195. </div>
  196. <div class="sop_c" v-if="c.matterType === 5">
  197. <span>{{ c.linkTitle }}</span>
  198. </div>
  199. <div class="sop_c" v-if="c.matterType === 2">
  200. <span>自定义视频</span>
  201. </div>
  202. <div class="sop_c" v-if="c.matterType === 4">
  203. <span>{{ c.fileName }}</span>
  204. </div>
  205. <div class="sop_foot">
  206. <span style="color: #999;">{{item.isSend ? '已发送' : ''}}</span>
  207. <div class="sop_btn" @click="handleSend(item.id, c)">发送</div>
  208. </div>
  209. </div>
  210. </div>
  211. </div>
  212. </div>
  213. </el-tab-pane>
  214. </el-tabs>
  215. </div>
  216. </div>
  217. </body>
  218. <script>
  219. new Vue({
  220. el: '#box',
  221. data() {
  222. return {
  223. httpUrl: '',
  224. bId: null,
  225. env: '',
  226. memberId: null,
  227. activeName: 'playing',
  228. executeData: [],
  229. sopItem: {},
  230. sopPlans: [],
  231. historyId: null,
  232. externalUserId: null,
  233. }
  234. },
  235. created() {
  236. this.bId = this.getQueryParam('bId')
  237. this.env = this.getQueryParam('env')
  238. if (!this.env || this.env === 'prod') {
  239. this.httpUrl = 'https://wlapi.wefanbot.com'
  240. } else {
  241. this.httpUrl = 'http://test.wefanbot.com:18993'
  242. }
  243. if (this.getQueryParam('memberId')) {
  244. // 已授权
  245. this.memberId = this.getQueryParam('memberId')
  246. this.getQyWxSign()
  247. } else {
  248. // 授权
  249. this.getAuth()
  250. }
  251. // this.memberId = "woU17nDAAAnoSca19vZVKiNEKdc9tyYQ"
  252. // this.externalUserId = "wmU17nDAAAGT2PF6G8PUHdSx2DY1ljpg"
  253. // this.executeSopList()
  254. },
  255. methods: {
  256. getAuth() {
  257. fetch(this.httpUrl + `/p/insuite/p/getRedirectUri?env=${this.env}&bId=${this.bId}`)
  258. .then(res => {
  259. return res.json()
  260. }).then(result => {
  261. let { data, code, msg } = result
  262. if (code === 1) {
  263. window.location.replace(data)
  264. } else {
  265. this.$message({
  266. message: msg,
  267. type: 'warning'
  268. })
  269. }
  270. })
  271. },
  272. getQyWxSign() {
  273. fetch(this.httpUrl + '/scrm/v1/wxcp-corp/p/getAgentConfig', {
  274. method: 'post',
  275. body: JSON.stringify({
  276. bId: this.bId,
  277. url: window.location.href,
  278. }),
  279. headers: {
  280. 'Content-Type': 'application/json'
  281. }
  282. }).then(res => {
  283. return res.json()
  284. }).then(result => {
  285. let { data, code, msg } = result
  286. if (code === 1) {
  287. let that = this
  288. wx.agentConfig({
  289. corpid: data.corpid,
  290. agentid: data.agentId,
  291. timestamp: data.timestamp, // 必填,生成签名的时间戳
  292. nonceStr: data.nonceStr, // 必填,生成签名的随机串
  293. signature: data.agentSignature, // 必填,签名,见附录1
  294. jsApiList: ['getCurExternalContact'], // 必填,需要使用的JS接口列表
  295. success: function (res) {
  296. // 回调
  297. // 此处直接在注入回调中调用了获取当前外部联系人userId的接口,注意此接口是从聊天框的工具栏进入才能获取
  298. wx.invoke('getCurExternalContact', {
  299. }, function (res) {
  300. if (res.err_msg == "getCurExternalContact:ok") {
  301. console.log('invoke成功:', res.userId);
  302. that.externalUserId = res.userId
  303. that.executeSopList()
  304. } else {
  305. //错误处理
  306. console.log('invoke失败:', res)
  307. }
  308. });
  309. },
  310. fail: function (res) {
  311. if (res.errMsg.indexOf('function not exist') > -1) {
  312. alert('版本过低请升级');
  313. }
  314. },
  315. complete: function (res) {
  316. // 回调
  317. console.log('complete1', res);
  318. }
  319. });
  320. } else {
  321. this.$message({
  322. message: msg,
  323. type: 'warning'
  324. })
  325. }
  326. })
  327. },
  328. handleClickTab(tab) {
  329. if (tab.name === 'playing') {
  330. this.executeSopList(0)
  331. } else {
  332. this.executeSopList(1)
  333. }
  334. },
  335. //获取执行中的sop计划
  336. executeSopList(status) {
  337. fetch(this.httpUrl + `/scrm/v1/wxcp-sop/p/executeSopList?externalUserId=${this.externalUserId}&memberId=${this.memberId}&bId=${this.bId}&status=${status || 0}`)
  338. .then(res => {
  339. return res.json()
  340. }).then(result => {
  341. let { data, code, msg } = result
  342. if (code === 1) {
  343. this.executeData = data
  344. if (data.length > 0) {
  345. this.executeData.forEach(i => {
  346. i.type = ''
  347. })
  348. this.getSop(data[0], status)
  349. }
  350. } else {
  351. this.$message({
  352. message: msg,
  353. type: 'warning'
  354. })
  355. }
  356. })
  357. },
  358. getSop (item, status) {
  359. this.sopItem = item
  360. this.executeData.forEach(i => {
  361. if (i.sopId !== item.sopId) {
  362. i.type = ''
  363. } else {
  364. i.type = 'success'
  365. }
  366. })
  367. this.getSopHistoryList(item, status)
  368. },
  369. // 获取sop内容
  370. getSopHistoryList(item, status) {
  371. fetch(this.httpUrl + `/scrm/v1/wxcp-sop/p/executeSopHistoryList?clientId=${item.clientId}&memberId=${this.memberId}&sopId=${item.sopId}&status=${status || 0}`)
  372. .then(res => {
  373. return res.json()
  374. }).then(result => {
  375. let { data, code, msg } = result
  376. if (code === 1) {
  377. this.sopPlans = data
  378. } else {
  379. this.$message({
  380. message: msg,
  381. type: 'warning'
  382. })
  383. }
  384. })
  385. },
  386. handleSend(historyId, c) {
  387. this.historyId = historyId
  388. if (c.matterType === 1 || c.matterType === 2 || c.matterType === 4) {
  389. fetch(this.httpUrl + `/scrm/v1/wxcp-sop/p/getMedia?planContentId=${c.id}`)
  390. .then(res => {
  391. return res.json()
  392. }).then(result => {
  393. let { data, code, msg } = result
  394. if (code === 1) {
  395. this.sendData(c, data.mediaId)
  396. } else {
  397. this.$message({
  398. message: msg,
  399. type: 'warning'
  400. })
  401. }
  402. })
  403. } else {
  404. this.sendData(c)
  405. }
  406. },
  407. sendData (c, mediaId) {
  408. fetch(this.httpUrl + '/scrm/v1/wxcp-corp/p/getAgentConfig', {
  409. method: 'post',
  410. body: JSON.stringify({
  411. bId: this.bId,
  412. url: window.location.href,
  413. }),
  414. headers: {
  415. 'Content-Type': 'application/json'
  416. }
  417. }).then(res => {
  418. return res.json()
  419. }).then(result => {
  420. let { data, code, msg } = result
  421. if (code === 1) {
  422. let that = this
  423. wx.agentConfig({
  424. corpid: data.corpid,
  425. agentid: data.agentId,
  426. timestamp: data.timestamp, // 必填,生成签名的时间戳
  427. nonceStr: data.nonceStr, // 必填,生成签名的随机串
  428. signature: data.agentSignature, // 必填,签名,见附录1
  429. jsApiList: ['sendChatMessage'], // 必填,需要使用的JS接口列表
  430. success: function (res) {
  431. // 回调
  432. if (c.matterType === 0) {
  433. wx.invoke('sendChatMessage', {
  434. msgtype: "text", //消息类型,必填
  435. enterChat: false, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
  436. text: {
  437. content: c.matterText, //文本内容
  438. },
  439. }, function (res) {
  440. if (res.err_msg == 'sendChatMessage:ok') {
  441. that.updateSop()
  442. //发送成功
  443. that.$message({
  444. message: '发送成功',
  445. type: 'success'
  446. })
  447. }
  448. })
  449. } else if (c.matterType === 1) {
  450. wx.invoke('sendChatMessage', {
  451. msgtype: "image", //消息类型,必填
  452. enterChat: false, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
  453. image:
  454. {
  455. mediaid: mediaId, //图片的素材id
  456. },
  457. }, function (res) {
  458. if (res.err_msg == 'sendChatMessage:ok') {
  459. that.updateSop()
  460. //发送成功
  461. that.$message({
  462. message: '发送成功',
  463. type: 'success'
  464. })
  465. }
  466. })
  467. } else if (c.matterType === 2) {
  468. wx.invoke('sendChatMessage', {
  469. msgtype: "video", //消息类型,必填
  470. enterChat: false, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
  471. video:
  472. {
  473. mediaid: mediaId, //视频的素材id
  474. },
  475. }, function (res) {
  476. if (res.err_msg == 'sendChatMessage:ok') {
  477. that.updateSop()
  478. //发送成功
  479. that.$message({
  480. message: '发送成功',
  481. type: 'success'
  482. })
  483. }
  484. })
  485. } else if (c.matterType === 4) {
  486. wx.invoke('sendChatMessage', {
  487. msgtype: "file", //消息类型,必填
  488. enterChat: false, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
  489. file:
  490. {
  491. mediaid: mediaId, //文件的素材id
  492. },
  493. }, function (res) {
  494. if (res.err_msg == 'sendChatMessage:ok') {
  495. that.updateSop()
  496. //发送成功
  497. that.$message({
  498. message: '发送成功',
  499. type: 'success'
  500. })
  501. }
  502. })
  503. } else if (c.matterType === 5) {
  504. wx.invoke('sendChatMessage', {
  505. msgtype: "news", //消息类型,必填
  506. enterChat: false, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
  507. news:
  508. {
  509. link: c.linkUrl, //H5消息页面url 必填
  510. title: c.linkTitle, //H5消息标题
  511. desc: c.linkDescription, //H5消息摘要
  512. imgUrl: c.linkThumbUrl, //H5消息封面图片URL
  513. }
  514. }, function (res) {
  515. if (res.err_msg == 'sendChatMessage:ok') {
  516. that.updateSop()
  517. //发送成功
  518. that.$message({
  519. message: '发送成功',
  520. type: 'success'
  521. })
  522. }
  523. })
  524. }
  525. },
  526. fail: function (res) {
  527. if (res.errMsg.indexOf('function not exist') > -1) {
  528. alert('版本过低请升级');
  529. }
  530. },
  531. complete: function (res) {
  532. // 回调
  533. console.log('complete', res);
  534. }
  535. });
  536. } else {
  537. this.$message({
  538. message: msg,
  539. type: 'warning'
  540. })
  541. }
  542. })
  543. },
  544. // 发送成功后刷新数据
  545. updateSop() {
  546. fetch(this.httpUrl + `/scrm/v1/wxcp-sop/p/updateHistorySendStatus?historyId=${this.historyId}`)
  547. .then(res => {
  548. return res.json()
  549. }).then(result => {
  550. let { data, code, msg } = result
  551. if (code === 1) {
  552. this.getSopHistoryList(this.sopItem)
  553. } else {
  554. this.$message({
  555. message: msg,
  556. type: 'warning'
  557. })
  558. }
  559. }).finally(() => {
  560. this.historyId = null
  561. })
  562. },
  563. // 截取url中的数据
  564. getQueryParam(paramName) {
  565. // 获取当前URL的查询字符串部分
  566. const queryString = window.location.search;
  567. // 创建一个URLSearchParams对象
  568. const urlParams = new URLSearchParams(queryString);
  569. // 返回指定参数的值,如果不存在则返回null
  570. return urlParams.get(paramName);
  571. },
  572. timeFormat(time, format = 'yyyy-MM-dd hh:mm:ss') {
  573. if (time === undefined || time === '' || time === null) {
  574. return '/';
  575. }
  576. const date = new Date(time);
  577. const o = {
  578. 'M+': date.getMonth() + 1, // 月份
  579. 'd+': date.getDate(), // 日
  580. 'h+': date.getHours(), // 小时
  581. 'm+': date.getMinutes(), // 分钟
  582. 's+': date.getSeconds(), // 秒
  583. 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
  584. 'S': date.getMilliseconds() // 毫秒
  585. };
  586. // 处理年份
  587. if (/(y+)/.test(format)) {
  588. format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
  589. }
  590. // 处理日期和时间部分
  591. for (let k in o) {
  592. if (new RegExp('(' + k + ')').test(format)) {
  593. let value = o[k];
  594. let padding = RegExp.$1.length === 1 ? '' : '00'; // 根据格式字符串中的长度决定是否补零
  595. format = format.replace(RegExp.$1, ('' + value).padStart(padding.length + value.toString().length - value.toString().length, '0'));
  596. }
  597. }
  598. // 如果格式只包含时间部分,移除日期部分可能的占位符
  599. if (!/(M+|d+)/.test(format)) {
  600. // 移除任何可能存在的日期占位符(如:'yyyy-MM-dd ')
  601. format = format.replace(/(\s*-\s*){2}/g, ''); // 移除两个'-'之间的任何内容
  602. format = format.replace(/^\s*yyyy-\s*/, ''); // 移除开头的'yyyy-'
  603. }
  604. // 如果格式只包含日期部分,移除时间部分可能的占位符
  605. if (!/(h+|m+|s+)/.test(format)) {
  606. // 移除任何可能存在的时间占位符(如:' hh:mm:ss')
  607. format = format.replace(/(\s*:\s*){2}/g, ''); // 移除两个':'之间的任何内容
  608. format = format.replace(/\s*hh:\s*$/, ''); // 移除结尾的' hh:'
  609. }
  610. return format;
  611. }
  612. }
  613. })
  614. </script>
  615. </html>