util.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. import { MessageBox } from 'element-ui'
  2. import TWEEN from '@tweenjs/tween.js'
  3. import twemoji from 'twemoji'
  4. import dayjs from 'dayjs'
  5. const timestampInterval = 1e3 * 60 * 3 // 3分钟的间隔时间
  6. const cryptoKey = 'dqWt6twz6JyEy3EZ'
  7. // 错误弹窗
  8. export function showError (msg, title = 'Error') {
  9. MessageBox.confirm(msg, title, {
  10. center: true,
  11. showCancelButton: false,
  12. showConfirmButton: false,
  13. callback () {}
  14. })
  15. }
  16. // 确认操作弹窗
  17. export function confirmPopup (msg, title = '提示') {
  18. return new Promise((resolve, reject) => {
  19. MessageBox.confirm(msg, '提示', {
  20. confirmButtonText: '确定',
  21. cancelButtonText: '取消',
  22. type: 'warning'
  23. })
  24. .then(() => {
  25. resolve()
  26. })
  27. .catch(() => {})
  28. })
  29. }
  30. /**
  31. * 判断系统||浏览器中英文
  32. * 对于不支持的浏览器 一律默认为 中文
  33. */
  34. export function getLanguage () {
  35. var language = (navigator.language || navigator.browserLanguage).toLowerCase()
  36. var locale = 'zh'
  37. if (language.indexOf('en') > -1) {
  38. locale = 'en'
  39. } else {
  40. locale = 'zh'
  41. }
  42. return locale
  43. }
  44. /**
  45. * 获取Url上指定参数
  46. * @param {String} name 参数名
  47. */
  48. export function getUrlParam (name) {
  49. var reg = new RegExp('[?&]' + name + '=([^&#?]*)(&|#|$)')
  50. var r = window.location.href.match(reg)
  51. return r ? r[1] : null
  52. }
  53. export var Cookie = {
  54. setCookie (name, value) {
  55. var Days = 7
  56. var exp = new Date()
  57. exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000)
  58. exp.setTime(exp.getTime() + 60 * 1000)
  59. if (
  60. window.location.port === '8080' ||
  61. /^test-|\.webdev2\./.test(window.location.host)
  62. ) {
  63. document.cookie =
  64. name + '=' + escape(value) + ';expires=' + exp.toGMTString()
  65. } else {
  66. document.cookie =
  67. name +
  68. '=' +
  69. escape(value) +
  70. ';domain=.mee.chat;path=/;expires=' +
  71. exp.toGMTString()
  72. }
  73. },
  74. getCookie (name) {
  75. var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)')
  76. var arr = document.cookie.match(reg)
  77. if (arr) {
  78. return unescape(arr[2])
  79. } else {
  80. return null
  81. }
  82. },
  83. delCookie (name) {
  84. var str1 = name + '=;domain=.mee.chat;path=/'
  85. str1 += ';expires=' + new Date(0).toGMTString()
  86. document.cookie = str1
  87. var str2 = name + '=;path=/'
  88. str2 += ';expires=' + new Date(0).toGMTString()
  89. document.cookie = str2
  90. }
  91. }
  92. /**
  93. * 获取聊天区域高度
  94. * @param {String} msg
  95. */
  96. export function getResizeHeight () {
  97. let clientHeight = document.documentElement.clientHeight
  98. let clientWidth = document.documentElement.clientWidth
  99. let topHeight = 61
  100. let botHeight = 181
  101. var chatBoxHeight
  102. if (clientHeight < 600) {
  103. chatBoxHeight = 600 - topHeight - botHeight
  104. }
  105. if (clientHeight < 800 || clientWidth < 1000) {
  106. chatBoxHeight = clientHeight - topHeight - botHeight
  107. } else {
  108. chatBoxHeight = clientHeight * 0.8 - topHeight - botHeight
  109. }
  110. return chatBoxHeight
  111. }
  112. function rc4 (str, key) {
  113. var s = []
  114. var j = 0
  115. var x
  116. var res = ''
  117. for (var i = 0; i < 256; i++) {
  118. s[i] = i
  119. }
  120. for (i = 0; i < 256; i++) {
  121. j = (j + s[i] + key.charCodeAt(i % key.length)) % 256
  122. x = s[i]
  123. s[i] = s[j]
  124. s[j] = x
  125. }
  126. i = 0
  127. j = 0
  128. for (var y = 0; y < str.length; y++) {
  129. i = (i + 1) % 256
  130. j = (j + s[i]) % 256
  131. x = s[i]
  132. s[i] = s[j]
  133. s[j] = x
  134. res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256])
  135. }
  136. return res
  137. }
  138. /**
  139. * 加密信息
  140. * @param {String} msg
  141. */
  142. export function cryptoMsg (msg) {
  143. let result
  144. try {
  145. result = btoa(rc4(encodeURIComponent(msg), cryptoKey))
  146. } catch {
  147. return msg
  148. }
  149. return result
  150. }
  151. /**
  152. * 解密信息
  153. * @param {String} msg
  154. */
  155. export function decryptoMsg (msg) {
  156. let result
  157. try {
  158. result = decodeURIComponent(rc4(atob(msg), cryptoKey))
  159. } catch {
  160. return msg
  161. }
  162. return result
  163. }
  164. const linkRule = new RegExp(
  165. '(https?|ftp|file)://[-a-zA-Z0-9/-_.?#!+%]+'
  166. )
  167. /**
  168. * 向数组添加数据
  169. * @param {Array} data
  170. */
  171. export function addSomeInArray (data) {
  172. let lastTime = null
  173. data.forEach(item => {
  174. // 添加timeMsg tag
  175. if (lastTime === null) {
  176. item.timeMsg = false
  177. } else {
  178. item.timeMsg = parseInt(item.timestamp) - lastTime > timestampInterval
  179. }
  180. lastTime = parseInt(item.timestamp)
  181. addLinkItem(item)
  182. })
  183. }
  184. /**
  185. * 单个添加timeMsg tag
  186. * @param {Array} data
  187. */
  188. export function addTimeMsgInItem (item, arr) {
  189. if (arr.length === 0) {
  190. item.timeMsg = true
  191. } else {
  192. let lastTime = parseInt(arr[arr.length - 1].timestamp)
  193. item.timeMsg = parseInt(item.timestamp) - lastTime > timestampInterval
  194. }
  195. }
  196. /**
  197. * 判断是否是移动端
  198. */
  199. export function isMobile () {
  200. return /Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)
  201. }
  202. /**
  203. * 获得meechat版本
  204. * */
  205. export function getMeechatType () {
  206. if (location.pathname.indexOf('mini') > -1) {
  207. return 'mini'
  208. } else if (isMobile()) {
  209. return 'h5'
  210. } else {
  211. return 'pc'
  212. }
  213. }
  214. function convertEntity (str) {
  215. // &colon;&rpar;
  216. const entityMap = {
  217. '&': '&amp;',
  218. '<': '&lt;',
  219. '>': '&gt;',
  220. '"': '&quot;',
  221. "'": '&apos;'
  222. }
  223. return str.replace(/[&<>'"]/g, function (matched) {
  224. return entityMap[matched]
  225. })
  226. }
  227. /*
  228. * 单个添加链接 msg_type = 10
  229. * @param {Array} data
  230. */
  231. export function addLinkItem (item) {
  232. // 先用实体处理
  233. if (item.msg_type == 0) {
  234. item.content = convertEntity(item.content)
  235. if (item.content.match(linkRule)) {
  236. item.content = item.content.replace(linkRule, a => {
  237. return `<a href="${a}" class="link text" target="_blank">${a}</a>`
  238. })
  239. }
  240. item.content = twemoji.parse(item.content, {
  241. callback: function (icon, options) {
  242. return 'https://w2.meechat.me/emoji/' + icon + '.svg'
  243. }
  244. })
  245. }
  246. }
  247. /**
  248. * 移除arr2中满足callback的arr1数组元素
  249. * @param {Array} arr1 [{hash:1}]
  250. * @param {Array} arr2 [{hash:1},{hash:2}]
  251. * @result arr1 => []
  252. */
  253. export function removeItemIfEixt (arr1, arr2, callback) {
  254. arr1.forEach((item, index) => {
  255. if (arr2.some(item2 => callback(item2) == callback(item))) {
  256. arr1.splice(index, 1)
  257. }
  258. })
  259. }
  260. /**
  261. * 格式化置顶消息
  262. */
  263. export function formatPinMsg (pinMsg, userId) {
  264. pinMsg.name = pinMsg.nick_name
  265. pinMsg.content = decryptoMsg(pinMsg.msg)
  266. pinMsg.type = pinMsg.from == userId ? 'me' : 'you'
  267. pinMsg.avatar = pinMsg.cover_photo || ''
  268. pinMsg.userId = pinMsg.from
  269. }
  270. /**
  271. * @param {store} state
  272. * @param {Number} createTime
  273. */
  274. export function dealErrorMsg (state, createTime) {
  275. state.chatList.forEach(item => {
  276. if (item.createTime == createTime) {
  277. item.fail = true
  278. item.loading = false
  279. }
  280. })
  281. }
  282. /**
  283. * 如果含有at他人的信息,格式化
  284. * @param {String} msg @heitan@aben 123
  285. * @param {Array} members
  286. * @result => {@start[10,9]end}@heitan@aben 123
  287. */
  288. export function encryptAtMsg (msg, members) {
  289. let ats = []
  290. members.forEach(user => {
  291. let reg = new RegExp(`@${user.nick_name}`)
  292. if (reg.test(msg)) {
  293. ats.push(user.user_id)
  294. }
  295. })
  296. if (ats.length) {
  297. msg = `{@start[${ats.toString()}]end}${msg}`
  298. }
  299. return msg
  300. }
  301. /**
  302. *
  303. * @param {String} msg
  304. * @return {Array|Null}
  305. */
  306. export function decryptAtMsg (msg) {
  307. let reg = /^{@start\[(.*)\]end}/
  308. let ret = reg.exec(msg)
  309. if (ret) {
  310. return ret[0].split(',')
  311. }
  312. }
  313. /**
  314. *判断是否是at我的信息
  315. */
  316. export function checkAtMe (msg, username) {
  317. if (!username) return false
  318. let reg = new RegExp(`@${username}`)
  319. return reg.test(msg)
  320. }
  321. export function scrollIntoView (node, offsetTop) {
  322. let distance = Math.abs(offsetTop - node.scrollTop)
  323. let time = distance > 500 ? 1000 : distance * 2
  324. let tw = new TWEEN.Tween(node)
  325. .to({ scrollTop: offsetTop }, time)
  326. .easing(TWEEN.Easing.Quadratic.Out)
  327. return tw.start()
  328. }
  329. export function scrollMsgIntoView (node, offsetTop, targetNode, callback) {
  330. scrollIntoView(node, offsetTop)
  331. .onComplete(() => {
  332. targetNode.classList.toggle('active')
  333. callback && callback()
  334. })
  335. setTimeout(() => {
  336. targetNode.classList.toggle('active')
  337. }, 3000)
  338. }
  339. export var noticeManager = {
  340. tabTimer: null, // tab切换定时器
  341. askPermission () {
  342. return new Promise((resolve, reject) => {
  343. if (!('Notification' in window)) {
  344. reject(new Error('This browser does not support desktop notification'))
  345. } else if (Notification.permission === 'granted') {
  346. resolve()
  347. } else if (Notification.permission === 'default ') {
  348. Notification.requestPermission(function (permission) {
  349. // 如果用户同意,就可以向他们发送通知
  350. if (permission === 'granted') {
  351. resolve()
  352. } else {
  353. reject(new Error())
  354. }
  355. })
  356. }
  357. })
  358. },
  359. showNotification (data) {
  360. // 开启全局消息免扰
  361. if (this.getGlobalNotice() == 1) return
  362. // 已经打开页面了,就不需要额外通知
  363. let userId = localStorage.getItem('user_id')
  364. // 自己发的消息不用通知
  365. if (userId == data.from) return
  366. this.askPermission().then(() => {
  367. let notification = new Notification(data.name, {
  368. body: '你收到了一条消息',
  369. icon: `/dist/img/icons/meechat.png`
  370. })
  371. // 打开页面
  372. let path
  373. if (data.group_id) {
  374. path = `/group/${data.group_id}`
  375. } else {
  376. let sessionId = +data.to > +data.from ? `${data.from}-${data.to}` : `${data.to}-${data.from}`
  377. path = `/pm/${sessionId}`
  378. }
  379. notification.onclick = () => {
  380. window.$router.push({ path })
  381. notification.close()
  382. window.focus()
  383. }
  384. setTimeout(() => {
  385. notification.close()
  386. }, 3500)
  387. })
  388. },
  389. changeTitle (num) {
  390. // 开启全局消息免扰
  391. // if (this.getGlobalNotice() == 1) return
  392. let title = num ? `MeeChat (有${num}条新消息)` : `MeeChat`
  393. document.title = title
  394. if (num) {
  395. num = num > 99 ? '99+' : num
  396. if (this.tabTimer) clearTimeout(this.tabTimer)
  397. // this.tabTimer = setTimeout(() => {
  398. // document.title = `MeeChat`
  399. // }, 1000)
  400. }
  401. },
  402. getGlobalNotice () {
  403. if (window.Notification && Notification.permission === 'granted') {
  404. let systemConfig = JSON.parse(localStorage.getItem('systemConfig') || '{}')
  405. return typeof systemConfig.mute === 'undefined' ? 1 : systemConfig.mute
  406. } else {
  407. return 1
  408. }
  409. },
  410. /**
  411. * @des 设置通知提醒
  412. * @param type {0:开启,1:关闭}
  413. * */
  414. setGlobalNotice (type, that) {
  415. let systemConfig = JSON.parse(localStorage.getItem('systemConfig') || '{}')
  416. if (type == 0) {
  417. Notification.requestPermission(function (permission) {
  418. // 如果用户同意,就可以向他们发送通知
  419. if (permission === 'granted') {
  420. systemConfig.mute = 0
  421. } else {
  422. systemConfig.mute = 1
  423. }
  424. that.openGlobalNotice = systemConfig.mute
  425. localStorage.setItem('systemConfig', JSON.stringify(systemConfig))
  426. })
  427. } else {
  428. systemConfig.mute = type
  429. that.openGlobalNotice = systemConfig.mute
  430. localStorage.setItem('systemConfig', JSON.stringify(systemConfig))
  431. }
  432. }
  433. }
  434. // pwa安装提示
  435. export var pwaManager = {
  436. init (button) {
  437. var deferredPrompt = null
  438. window.addEventListener('beforeinstallprompt', function (e) {
  439. deferredPrompt = e
  440. // 取消默认事件
  441. e.preventDefault()
  442. return false
  443. })
  444. button.addEventListener('click', function () {
  445. if (deferredPrompt != null) {
  446. deferredPrompt.prompt()
  447. // 检测用户的安装行为
  448. deferredPrompt.userChoice.then(function (choiceResult) {
  449. console.log(choiceResult.outcome)
  450. })
  451. deferredPrompt = null
  452. }
  453. })
  454. }
  455. }
  456. /**
  457. * @des 转换消息时间
  458. * @param {timeStamp} 时间戳
  459. * @param {type} 类型{1:会话列表,2:信息段}
  460. */
  461. export function formatMsgTime (timeStamp, type = 1, that) {
  462. if (!timeStamp) return ''
  463. let lastDate = dayjs().subtract(1, 'days').format('YYYY-MM-DD') // 昨天
  464. let inputDay = dayjs(timeStamp * 1)
  465. let inputDate = inputDay.format('YYYY-MM-DD')
  466. switch (type) {
  467. // 会话列表
  468. case 1:
  469. if (inputDate < lastDate) {
  470. return inputDay.format('YY/MM/DD')
  471. } else if (lastDate == inputDate) {
  472. return that.$t('chat.yesterday')
  473. } else {
  474. return inputDay.format('HH:mm')
  475. }
  476. // 信息段
  477. case 2:
  478. if (inputDate < lastDate) {
  479. return inputDay.format('MM-DD HH:mm')
  480. } else if (lastDate == inputDate) {
  481. return that.$t('chat.yesterday') + ` ${inputDay.format('HH:mm')}`
  482. } else {
  483. return inputDay.format('HH:mm')
  484. }
  485. }
  486. }
  487. /**
  488. * @des 跳去h5登录页
  489. * */
  490. export function toLoginPage () {
  491. let url = encodeURIComponent(location.hash.replace('#', ''))
  492. location.replace(`${location.origin + location.pathname}#/login?from=${url}`)
  493. // rounter.replace(`/login?from=${url}`)
  494. }
  495. /**
  496. * @des 将图片地址转成base64
  497. */
  498. export function getBase64 (imgUrl, callback) {
  499. window.URL = window.URL || window.webkitURL
  500. var xhr = new XMLHttpRequest()
  501. xhr.open('get', imgUrl, true)
  502. // 至关重要
  503. xhr.responseType = 'blob'
  504. xhr.onload = function () {
  505. if (this.status == 200) {
  506. // 得到一个blob对象
  507. var blob = this.response
  508. // 至关重要
  509. let oFileReader = new FileReader()
  510. oFileReader.onloadend = function (e) {
  511. let base64 = e.target.result
  512. callback && callback(base64)
  513. }
  514. oFileReader.readAsDataURL(blob)
  515. }
  516. }
  517. xhr.send()
  518. }
  519. /**
  520. * @des 图片懒加载
  521. * @param {el} wrap 图片容器
  522. * @param {HTMLCollection} imageArr 图片列表
  523. * @param {String} derection 整体列表滑动方向['down','up']
  524. */
  525. export function lazyloadImage ({ wrap, imageArr, derection = 'down' }) {
  526. if (!imageArr || imageArr.length <= 0) return
  527. let imageLen = imageArr.length
  528. let listScrollTop = wrap.scrollTop
  529. let listClientHeight = wrap.clientHeight
  530. switch (derection) {
  531. case 'down':
  532. for (let i = 0; i < imageLen; i++) {
  533. let item = imageArr[i]
  534. let originUrl = item.getAttribute('originurl')
  535. let url = item.getAttribute('src')
  536. if (item.offsetTop - listScrollTop <= listClientHeight) {
  537. if (originUrl && originUrl != url) item.setAttribute('src', originUrl)
  538. } else {
  539. return i >= 1 ? i - 1 : 0
  540. }
  541. }
  542. break
  543. case 'up':
  544. for (let i = imageLen - 1; i >= 0; i--) {
  545. let item = imageArr[i]
  546. let originUrl = item.getAttribute('originurl')
  547. let url = item.getAttribute('src')
  548. let top = item.getBoundingClientRect().top
  549. if (top >= -100 && top < listClientHeight + 300) {
  550. if (originUrl && originUrl != url) item.setAttribute('src', originUrl)
  551. }
  552. }
  553. break
  554. }
  555. }
  556. /**
  557. * @des 获取头像背景色
  558. * @param {*} str 房间号
  559. * @param {*} userId 当前用户id
  560. */
  561. export function getAvatarBgColor (str, userId) {
  562. str += ''
  563. if (str.match('-')) {
  564. let num = 0
  565. str.split('-').forEach(e => {
  566. if (e !== userId) {
  567. num = e % 9
  568. }
  569. })
  570. return num + ''
  571. } else {
  572. return str % 9 + ''
  573. }
  574. }
  575. /**
  576. * @des 打开一个空白窗口
  577. * @param {Number} iWidth 弹出窗口的宽度
  578. * @param {Number} iHeight 弹出窗口的高度
  579. */
  580. export function openBlankWindow (iWidth = 500, iHeight = 600) {
  581. var iTop = (window.screen.availHeight - 30 - iHeight) / 2 // 获得窗口的垂直位置;
  582. var iLeft = (window.screen.availWidth - 10 - iWidth) / 2 // 获得窗口的水平位置;
  583. var winHandler = window.open('', '_blank', 'height=' + iHeight + ', width=' + iWidth + ', top=' + iTop + ', left=' + iLeft + ', toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no')
  584. return winHandler
  585. }