util.js 16 KB

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