timeOut.html 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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"
  7. content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no, viewport-fit=cover" />
  8. <title>超时消息列表</title>
  9. <!--引入 element-ui 的样式,-->
  10. <link rel="stylesheet"
  11. href="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1742018383195/element-ui.css">
  12. <!-- 必须先引入vue, 后使用element-ui -->
  13. <script
  14. src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1742017957144/vue.js"></script>
  15. <!-- 引入element 的组件库-->
  16. <script
  17. src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1742017747738/element-ui.js"></script>
  18. <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  19. <script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
  20. <!-- <script src="js/vconsole.min.js"></script>
  21. <script>
  22. var vConsole = new window.VConsole();
  23. </script> -->
  24. </head>
  25. <style>
  26. body {
  27. margin: 0;
  28. padding: 0;
  29. }
  30. .box {
  31. width: 100vw;
  32. height: 100vh;
  33. box-sizing: border-box;
  34. background: #FAFAFA;
  35. }
  36. .page4 {
  37. width: 100vw;
  38. height: 100vh;
  39. box-sizing: border-box;
  40. }
  41. .client_list {
  42. padding: 15px 10px;
  43. height: 100%;
  44. overflow: auto;
  45. box-sizing: border-box;
  46. }
  47. .client_item {
  48. width: 100%;
  49. background: #FFFFFF;
  50. border-radius: 10px;
  51. padding: 20px 10px;
  52. box-sizing: border-box;
  53. margin-bottom: 15px;
  54. display: flex;
  55. align-items: center;
  56. justify-content: space-between;
  57. }
  58. .client_head {
  59. font-weight: 400;
  60. font-size: 14px;
  61. color: #222222;
  62. }
  63. .client_right {
  64. display: flex;
  65. align-items: center;
  66. font-weight: 400;
  67. font-size: 14px;
  68. color: #999999;
  69. }
  70. .to_iocn {
  71. width: 14px;
  72. height: 14px;
  73. }
  74. </style>
  75. <body>
  76. <div id="box" class="box">
  77. <!-- 数据查看 -->
  78. <div class="page4">
  79. <div class="client_list">
  80. <div class="client_item" v-for="(item, index) in timeOutList" :key="index" @click="handleTimeOut(item)">
  81. <div class="client_head">{{item.clientName}}</div>
  82. <div class="client_right">已等待
  83. <span style="color: #FF4E4E;">{{formatTime(item.waitSeconds)}}</span>
  84. <img class="to_iocn" src="./img/to-icon.png" alt="">
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. </div>
  90. </body>
  91. <script>
  92. new Vue({
  93. el: '#box',
  94. data() {
  95. return {
  96. httpUrl: '',
  97. bId: null,
  98. env: '',
  99. memberId: null,
  100. timeOutList: [],
  101. }
  102. },
  103. created() {
  104. this.bId = this.getQueryParam('bId')
  105. this.env = this.getQueryParam('env')
  106. if (!this.env || this.env === 'prod') {
  107. this.httpUrl = 'https://wlapi.wefanbot.com'
  108. } else {
  109. this.httpUrl = 'http://test.wefanbot.com:18993'
  110. }
  111. if (this.getQueryParam('memberId')) {
  112. if (this.isUnknownType(this.getQueryParam('memberId'))) {
  113. this.$message({
  114. message: '员工未激活',
  115. type: 'warning'
  116. })
  117. } else {
  118. // 已授权
  119. this.memberId = this.getQueryParam('memberId')
  120. this.getQyWxSign()
  121. this.executeDetail()
  122. }
  123. } else {
  124. // 授权
  125. this.getAuth()
  126. }
  127. },
  128. methods: {
  129. getAuth() {
  130. fetch(this.httpUrl + `/p/insuite/p/getRedirectUri?env=${this.env}&bId=${this.bId}`)
  131. .then(res => {
  132. return res.json()
  133. }).then(result => {
  134. let { data, code, msg } = result
  135. if (code === 1) {
  136. window.location.replace(data)
  137. } else {
  138. this.$message({
  139. message: msg,
  140. type: 'warning'
  141. })
  142. }
  143. })
  144. },
  145. getQyWxSign() {
  146. fetch(this.httpUrl + '/scrm/v1/wxcp-corp/p/getAgentConfig', {
  147. method: 'post',
  148. body: JSON.stringify({
  149. bid: this.bId,
  150. url: window.location.href,
  151. }),
  152. headers: {
  153. 'Content-Type': 'application/json'
  154. }
  155. }).then(res => {
  156. return res.json()
  157. }).then(result => {
  158. let { data, code, msg } = result
  159. if (code === 1) {
  160. wx.config({
  161. beta: true,
  162. debug: false,
  163. appId: data.corpid, // 必填,企业号的唯一标识,此处填写企业号corpid
  164. timestamp: data.timestamp, // 必填,生成签名的时间戳
  165. nonceStr: data.nonceStr, // 必填,生成签名的随机串
  166. signature: data.agentSignature, // 必填,签名,见附录1
  167. jsApiList: ['checkJsApi', 'openEnterpriseChat'] // 必填,需要使用的JS接口列表
  168. })
  169. wx.agentConfig({
  170. corpid: data.corpid,
  171. agentid: data.agentId,
  172. timestamp: data.timestamp, // 必填,生成签名的时间戳
  173. nonceStr: data.nonceStr, // 必填,生成签名的随机串
  174. signature: data.agentSignature, // 必填,签名,见附录1
  175. jsApiList: ['openEnterpriseChat'], // 必填,需要使用的JS接口列表
  176. success: function (res) {
  177. // 回调
  178. console.log('agentConfig成功', res);
  179. },
  180. fail: function (res) {
  181. if (res.errMsg.indexOf('function not exist') > -1) {
  182. alert('版本过低请升级');
  183. }
  184. },
  185. complete: function (res) {
  186. // 回调
  187. console.log('complete', res);
  188. }
  189. });
  190. } else {
  191. this.$message({
  192. message: msg,
  193. type: 'warning'
  194. })
  195. }
  196. })
  197. },
  198. executeDetail() {
  199. fetch(this.httpUrl + `/wxcp/monitoredMessage/p/member/wait-handle-message?memberId=${this.memberId}`)
  200. .then(res => {
  201. return res.json()
  202. }).then(result => {
  203. let { data, code, msg } = result
  204. if (code === 1) {
  205. this.timeOutList = data || []
  206. } else {
  207. this.$message({
  208. message: msg,
  209. type: 'warning'
  210. })
  211. }
  212. })
  213. },
  214. handleTimeOut(item) {
  215. if (item.sessionType === 1) {
  216. this.toFollowGroup(item)
  217. } else {
  218. this.toFollowClient(item)
  219. }
  220. },
  221. toFollowGroup(item) {
  222. wx.openEnterpriseChat({
  223. groupName: "", // 会话名称。单聊时该参数传入空字符串""即可。
  224. chatId: item.chatId,
  225. success: function (res) {
  226. var chatId = res.chatId; //返回当前群聊ID,仅当使用agentConfig注入该接口权限时才返回chatId
  227. // 回调
  228. console.log('打开客户群聊成功', res)
  229. },
  230. fail: function (res) {
  231. // 回调
  232. if (res.errMsg.indexOf('function not exist') > -1) {
  233. alert('版本过低请升级');
  234. }
  235. console.log('失败', res);
  236. },
  237. complete: function (res) {
  238. // 回调
  239. console.log('完成', res);
  240. }
  241. });
  242. },
  243. toFollowClient (item) {
  244. wx.openEnterpriseChat({
  245. externalUserIds: item.wxcpClientExternalUserId, // 参与会话的外部联系人列表,格式为userId1;userId2;…,用分号隔开。
  246. groupName: "", // 会话名称。单聊时该参数传入空字符串""即可。
  247. chatId: "",
  248. success: function (res) {
  249. var chatId = res.chatId; //返回当前群聊ID,仅当使用agentConfig注入该接口权限时才返回chatId
  250. // 回调
  251. console.log('打开客户成功', res)
  252. },
  253. fail: function (res) {
  254. // 回调
  255. if (res.errMsg.indexOf('function not exist') > -1) {
  256. alert('版本过低请升级');
  257. }
  258. console.log('失败', res);
  259. },
  260. complete: function (res) {
  261. // 回调
  262. console.log('完成', res);
  263. }
  264. })
  265. },
  266. formatTime(seconds) {
  267. // 处理无效输入(空值/非数字)
  268. if (seconds == null || seconds === '' || typeof seconds !== 'number') {
  269. return '--';
  270. }
  271. // 确定时间符号和绝对值
  272. const isNegative = seconds < 0;
  273. const absSeconds = Math.abs(seconds);
  274. // 内部格式化函数(处理绝对值)
  275. const format = (val, unit) => {
  276. const num = Math.round(val);
  277. return `${num}${unit}${num > 1 ? '们' : ''}`;
  278. };
  279. // 根据绝对值大小转换单位
  280. let formatted;
  281. if (absSeconds >= 3600) {
  282. formatted = format(absSeconds / 3600, '小时');
  283. } else if (absSeconds >= 60) {
  284. formatted = format(absSeconds / 60, '分钟');
  285. } else {
  286. formatted = format(absSeconds, '秒');
  287. }
  288. // 添加负号(当原始值为负时)
  289. return isNegative ? `-${formatted}` : formatted;
  290. },
  291. formatNumber(num) {
  292. const number = Number(num);
  293. if (isNaN(number) || !isFinite(number)) {
  294. return num;
  295. }
  296. // 整数直接返回
  297. if (Number.isInteger(number)) {
  298. return number.toString();
  299. }
  300. // 非整数处理:先保留两位小数,再移除末尾的零
  301. let str = number.toFixed(2);
  302. // 正则表达式移除末尾的零(保留有效小数位)
  303. return str.replace(/(\.[1-9]?0+)$|(\.[1-9])0+$/g, '$1$2');
  304. },
  305. // 截取url中的数据
  306. getQueryParam(paramName) {
  307. // 获取当前URL的查询字符串部分
  308. const queryString = window.location.search;
  309. // 创建一个URLSearchParams对象
  310. const urlParams = new URLSearchParams(queryString);
  311. // 返回指定参数的值,如果不存在则返回null
  312. return urlParams.get(paramName);
  313. },
  314. isUnknownType(value) {
  315. // 1. 先检查是否为字符串类型
  316. // 2. 再检查是否以"unknown"开头(区分大小写)
  317. return typeof value === 'string' && value.startsWith('unknown');
  318. }
  319. }
  320. })
  321. </script>
  322. </html>