socket.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. import { reactive, ref, unref } from 'vue';
  2. import sheep from '@/sheep';
  3. // import chat from '@/sheep/api/chat';
  4. import dayjs from 'dayjs';
  5. import io from '@hyoga/uni-socket.io';
  6. export function useChatWebSocket(socketConfig) {
  7. let SocketIo = null;
  8. // chat状态数据
  9. const state = reactive({
  10. chatDotNum: 0, //总状态红点
  11. chatList: [], //会话信息
  12. customerUserInfo: {}, //用户信息
  13. customerServerInfo: {
  14. //客服信息
  15. title: '连接中...',
  16. state: 'connecting',
  17. avatar: null,
  18. nickname: '',
  19. },
  20. socketState: {
  21. isConnect: true, //是否连接成功
  22. isConnecting: false, //重连中,不允许新的socket开启。
  23. tip: '',
  24. },
  25. chatHistoryPagination: {
  26. page: 0, //当前页
  27. list_rows: 10, //每页条数
  28. last_id: 0, //最后条ID
  29. lastPage: 0, //总共多少页
  30. loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
  31. },
  32. templateChatList: [], //猜你想问
  33. chatConfig: {}, // 配置信息
  34. isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
  35. });
  36. /**
  37. * 连接初始化
  38. * @param {Object} config - 配置信息
  39. * @param {Function} callBack -回调函数,有新消息接入,保持底部
  40. */
  41. const socketInit = (config, callBack) => {
  42. state.chatConfig = config;
  43. if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false
  44. if (state.socketState.isConnecting) return; // 重连中,返回false
  45. // 启动初始化
  46. SocketIo = io(config.chat_domain, {
  47. reconnection: true, // 默认 true 是否断线重连
  48. reconnectionAttempts: 5, // 默认无限次 断线尝试次数
  49. reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。
  50. reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000
  51. randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
  52. timeout: 20000, // 默认 20s
  53. transports: ['websocket', 'polling'], // websocket | polling,
  54. ...config,
  55. });
  56. // 监听连接
  57. SocketIo.on('connect', async (res) => {
  58. socketReset(callBack);
  59. // socket连接
  60. // 用户登录
  61. // 顾客登录
  62. console.log('socket:connect');
  63. });
  64. // 监听消息
  65. SocketIo.on('message', (res) => {
  66. if (res.error === 0) {
  67. const { message, sender } = res.data;
  68. state.chatList.push(formatMessage(res.data.message));
  69. // 告诉父级页面
  70. // window.parent.postMessage({
  71. // chatDotNum: ++state.chatDotNum
  72. // })
  73. callBack && callBack();
  74. }
  75. });
  76. // 监听客服接入成功
  77. SocketIo.on('customer_service_access', (res) => {
  78. if (res.error === 0) {
  79. editCustomerServerInfo({
  80. title: res.data.customer_service.name,
  81. state: 'online',
  82. avatar: res.data.customer_service.avatar,
  83. });
  84. state.chatList.push(formatMessage(res.data.message));
  85. // callBack && callBack()
  86. }
  87. });
  88. // 监听排队等待
  89. SocketIo.on('waiting_queue', (res) => {
  90. if (res.error === 0) {
  91. editCustomerServerInfo({
  92. title: res.data.title,
  93. state: 'waiting',
  94. avatar: '',
  95. });
  96. // callBack && callBack()
  97. }
  98. });
  99. // 监听没有客服在线
  100. SocketIo.on('no_customer_service', (res) => {
  101. if (res.error === 0) {
  102. editCustomerServerInfo({
  103. title: '暂无客服在线...',
  104. state: 'waiting',
  105. avatar: '',
  106. });
  107. }
  108. state.chatList.push(formatMessage(res.data.message));
  109. // callBack && callBack()
  110. });
  111. // 监听客服上线
  112. SocketIo.on('customer_service_online', (res) => {
  113. if (res.error === 0) {
  114. editCustomerServerInfo({
  115. title: res.data.customer_service.name,
  116. state: 'online',
  117. avatar: res.data.customer_service.avatar,
  118. });
  119. }
  120. });
  121. // 监听客服下线
  122. SocketIo.on('customer_service_offline', (res) => {
  123. if (res.error === 0) {
  124. editCustomerServerInfo({
  125. title: res.data.customer_service.name,
  126. state: 'offline',
  127. avatar: res.data.customer_service.avatar,
  128. });
  129. }
  130. });
  131. // 监听客服忙碌
  132. SocketIo.on('customer_service_busy', (res) => {
  133. if (res.error === 0) {
  134. editCustomerServerInfo({
  135. title: res.data.customer_service.name,
  136. state: 'busy',
  137. avatar: res.data.customer_service.avatar,
  138. });
  139. }
  140. });
  141. // 监听客服断开链接
  142. SocketIo.on('customer_service_break', (res) => {
  143. if (res.error === 0) {
  144. editCustomerServerInfo({
  145. title: '客服服务结束',
  146. state: 'offline',
  147. avatar: '',
  148. });
  149. state.socketState.isConnect = false;
  150. state.socketState.tip = '当前服务已结束';
  151. }
  152. state.chatList.push(formatMessage(res.data.message));
  153. // callBack && callBack()
  154. });
  155. // 监听自定义错误 custom_error
  156. SocketIo.on('custom_error', (error) => {
  157. editCustomerServerInfo({
  158. title: error.msg,
  159. state: 'offline',
  160. avatar: '',
  161. });
  162. console.log('custom_error:', error);
  163. });
  164. // 监听错误 error
  165. SocketIo.on('error', (error) => {
  166. console.log('error:', error);
  167. });
  168. // 重连失败 connect_error
  169. SocketIo.on('connect_error', (error) => {
  170. console.log('connect_error');
  171. });
  172. // 连接上,但无反应 connect_timeout
  173. SocketIo.on('connect_timeout', (error) => {
  174. console.log(error, 'connect_timeout');
  175. });
  176. // 服务进程销毁 disconnect
  177. SocketIo.on('disconnect', (error) => {
  178. console.log(error, 'disconnect');
  179. });
  180. // 服务重启重连上reconnect
  181. SocketIo.on('reconnect', (error) => {
  182. console.log(error, 'reconnect');
  183. });
  184. // 开始重连reconnect_attempt
  185. SocketIo.on('reconnect_attempt', (error) => {
  186. state.socketState.isConnect = false;
  187. state.socketState.isConnecting = true;
  188. editCustomerServerInfo({
  189. title: `重连中,第${error}次尝试...`,
  190. state: 'waiting',
  191. avatar: '',
  192. });
  193. console.log(error, 'reconnect_attempt');
  194. });
  195. // 重新连接中reconnecting
  196. SocketIo.on('reconnecting', (error) => {
  197. console.log(error, 'reconnecting');
  198. });
  199. // 重新连接错误reconnect_error
  200. SocketIo.on('reconnect_error', (error) => {
  201. console.log('reconnect_error');
  202. });
  203. // 重新连接失败reconnect_failed
  204. SocketIo.on('reconnect_failed', (error) => {
  205. state.socketState.isConnecting = false;
  206. editCustomerServerInfo({
  207. title: `重连失败,请刷新重试~`,
  208. state: 'waiting',
  209. avatar: '',
  210. });
  211. console.log(error, 'reconnect_failed');
  212. // setTimeout(() => {
  213. state.isSendSucces = 1;
  214. // }, 500)
  215. });
  216. };
  217. // 重置socket
  218. const socketReset = (callBack) => {
  219. state.chatList = [];
  220. state.chatHistoryList = [];
  221. state.chatHistoryPagination = {
  222. page: 0,
  223. per_page: 10,
  224. last_id: 0,
  225. totalPage: 0,
  226. };
  227. socketConnection(callBack); // 连接
  228. };
  229. // 退出连接
  230. const socketClose = () => {
  231. SocketIo.emit('customer_logout', {}, (res) => {
  232. console.log('socket:退出', res);
  233. });
  234. };
  235. // 测试事件
  236. const socketTest = () => {
  237. SocketIo.emit('test', {}, (res) => {
  238. console.log('test:test', res);
  239. });
  240. };
  241. // 发送消息
  242. const socketSendMsg = (data, sendMsgCallBack) => {
  243. state.isSendSucces = -1;
  244. state.chatList.push(data);
  245. sendMsgCallBack && sendMsgCallBack();
  246. SocketIo.emit(
  247. 'message',
  248. {
  249. message: formatInput(data),
  250. ...data.customData,
  251. },
  252. (res) => {
  253. // setTimeout(() => {
  254. state.isSendSucces = res.error;
  255. // }, 500)
  256. // console.log(res, 'socket:send');
  257. // sendMsgCallBack && sendMsgCallBack()
  258. },
  259. );
  260. };
  261. // 连接socket,存入sessionId
  262. const socketConnection = (callBack) => {
  263. SocketIo.emit(
  264. 'connection',
  265. {
  266. auth: 'user',
  267. token: uni.getStorageSync('socketUserToken') || '',
  268. session_id: uni.getStorageSync('socketSessionId') || '',
  269. },
  270. (res) => {
  271. if (res.error === 0) {
  272. socketCustomerLogin(callBack);
  273. uni.setStorageSync('socketSessionId', res.data.session_id);
  274. // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(
  275. // 'socketUserToken')) // 如果有用户token,绑定
  276. state.customerUserInfo = res.data.chat_user;
  277. state.socketState.isConnect = true;
  278. } else {
  279. editCustomerServerInfo({
  280. title: `服务器异常!`,
  281. state: 'waiting',
  282. avatar: '',
  283. });
  284. state.socketState.isConnect = false;
  285. }
  286. },
  287. );
  288. };
  289. // 用户id,获取token
  290. const getUserToken = async (id) => {
  291. return uni.getStorageSync('token');
  292. };
  293. // 用户登录
  294. const socketLogin = (token) => {
  295. SocketIo.emit(
  296. 'login',
  297. {
  298. token: token,
  299. },
  300. (res) => {
  301. console.log(res, 'socket:login');
  302. state.customerUserInfo = res.data.chat_user;
  303. },
  304. );
  305. };
  306. // 顾客登录
  307. const socketCustomerLogin = (callBack) => {
  308. SocketIo.emit(
  309. 'customer_login',
  310. {
  311. room_id: state.chatConfig.room_id,
  312. },
  313. (res) => {
  314. state.templateChatList = res.data.questions.length ? res.data.questions : [];
  315. state.chatList.push({
  316. from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
  317. mode: 'template', // goods,order,image,text,system
  318. date: new Date().getTime(), //时间
  319. content: {
  320. //内容
  321. list: state.templateChatList,
  322. },
  323. });
  324. res.error === 0 && socketHistoryList(callBack);
  325. },
  326. );
  327. };
  328. // 获取历史消息
  329. const socketHistoryList = (historyCallBack) => {
  330. state.chatHistoryPagination.loadStatus = 'loading';
  331. state.chatHistoryPagination.page += 1;
  332. SocketIo.emit('messages', state.chatHistoryPagination, (res) => {
  333. if (res.error === 0) {
  334. state.chatHistoryPagination.total = res.data.messages.total;
  335. state.chatHistoryPagination.lastPage = res.data.messages.last_page;
  336. state.chatHistoryPagination.page = res.data.messages.current_page;
  337. res.data.messages.data.forEach((item) => {
  338. item.message_type && state.chatList.unshift(formatMessage(item));
  339. });
  340. state.chatHistoryPagination.loadStatus =
  341. state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage
  342. ? 'loadmore'
  343. : 'nomore';
  344. if (state.chatHistoryPagination.last_id == 0) {
  345. state.chatHistoryPagination.last_id = res.data.messages.data.length
  346. ? res.data.messages.data[0].id
  347. : 0;
  348. }
  349. state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();
  350. }
  351. // 历史记录之后,猜你想问
  352. // state.chatList.push({
  353. // from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
  354. // mode: 'template', // goods,order,image,text,system
  355. // date: new Date().getTime(), //时间
  356. // content: { //内容
  357. // list: state.templateChatList
  358. // }
  359. // })
  360. });
  361. };
  362. // 修改客服信息
  363. const editCustomerServerInfo = (data) => {
  364. state.customerServerInfo = {
  365. ...state.customerServerInfo,
  366. ...data,
  367. };
  368. };
  369. /**
  370. * ================
  371. * 工具函数 ↓
  372. * ===============
  373. */
  374. /**
  375. * 是否显示时间
  376. * @param {*} item - 数据
  377. * @param {*} index - 索引
  378. */
  379. const showTime = (item, index) => {
  380. if (unref(state.chatList)[index + 1]) {
  381. let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();
  382. if (dateString === dayjs(unref(item).date).fromNow()) {
  383. return false;
  384. } else {
  385. dateString = dayjs(unref(item).date).fromNow();
  386. return true;
  387. }
  388. }
  389. return false;
  390. };
  391. /**
  392. * 格式化时间
  393. * @param {*} time - 时间戳
  394. */
  395. const formatTime = (time) => {
  396. let diffTime = new Date().getTime() - time;
  397. if (diffTime > 28 * 24 * 60 * 1000) {
  398. return dayjs(time).format('MM/DD HH:mm');
  399. }
  400. if (diffTime > 360 * 28 * 24 * 60 * 1000) {
  401. return dayjs(time).format('YYYY/MM/DD HH:mm');
  402. }
  403. return dayjs(time).fromNow();
  404. };
  405. /**
  406. * 获取焦点
  407. * @param {*} virtualNode - 节点信息 ref
  408. */
  409. const getFocus = (virtualNode) => {
  410. if (window.getSelection) {
  411. let chatInput = unref(virtualNode);
  412. chatInput.focus();
  413. let range = window.getSelection();
  414. range.selectAllChildren(chatInput);
  415. range.collapseToEnd();
  416. } else if (document.selection) {
  417. let range = document.selection.createRange();
  418. range.moveToElementText(chatInput);
  419. range.collapse(false);
  420. range.select();
  421. }
  422. };
  423. /**
  424. * 文件上传
  425. * @param {Blob} file -文件数据流
  426. * @return {path,fullPath}
  427. */
  428. const upload = (name, file) => {
  429. return new Promise((resolve, reject) => {
  430. let data = new FormData();
  431. data.append('file', file, name);
  432. data.append('group', 'chat');
  433. ajax({
  434. url: '/upload',
  435. method: 'post',
  436. headers: {
  437. 'Content-Type': 'multipart/form-data',
  438. },
  439. data,
  440. success: function (res) {
  441. resolve(res);
  442. },
  443. error: function (err) {
  444. reject(err);
  445. },
  446. });
  447. });
  448. };
  449. /**
  450. * 粘贴到输入框
  451. * @param {*} e - 粘贴内容
  452. * @param {*} uploadHttp - 上传图片地址
  453. */
  454. const onPaste = async (e) => {
  455. let paste = e.clipboardData || window.clipboardData;
  456. let filesArr = Array.from(paste.files);
  457. filesArr.forEach(async (child) => {
  458. if (child && child.type.includes('image')) {
  459. e.preventDefault(); //阻止默认
  460. let file = child;
  461. const img = await readImg(file);
  462. const blob = await compressImg(img, file.type);
  463. const { data } = await upload(file.name, blob);
  464. let image = `<img class="full-url" src='${data.fullurl}'>`;
  465. document.execCommand('insertHTML', false, image);
  466. } else {
  467. document.execCommand('insertHTML', false, paste.getData('text'));
  468. }
  469. });
  470. };
  471. /**
  472. * 拖拽到输入框
  473. * @param {*} e - 粘贴内容
  474. * @param {*} uploadHttp - 上传图片地址
  475. */
  476. const onDrop = async (e) => {
  477. e.preventDefault(); //阻止默认
  478. let filesArr = Array.from(e.dataTransfer.files);
  479. filesArr.forEach(async (child) => {
  480. if (child && child.type.includes('image')) {
  481. let file = child;
  482. const img = await readImg(file);
  483. const blob = await compressImg(img, file.type);
  484. const { data } = await upload(file.name, blob);
  485. let image = `<img class="full-url" src='${data.fullurl}' >`;
  486. document.execCommand('insertHTML', false, image);
  487. } else {
  488. ElMessage({
  489. message: '禁止拖拽非图片资源',
  490. type: 'warning',
  491. });
  492. }
  493. });
  494. };
  495. /**
  496. * 解析富文本输入框内容
  497. * @param {*} virtualNode -节点信息
  498. * @param {Function} formatInputCallBack - cb 回调
  499. */
  500. const formatChatInput = (virtualNode, formatInputCallBack) => {
  501. let res = '';
  502. let elemArr = Array.from(virtualNode.childNodes);
  503. elemArr.forEach((child, index) => {
  504. if (child.nodeName === '#text') {
  505. //如果为文本节点
  506. res += child.nodeValue;
  507. if (
  508. //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。
  509. elemArr[index + 1] &&
  510. elemArr[index + 1].nodeName === 'IMG' &&
  511. elemArr[index + 1] &&
  512. elemArr[index + 1].name !== 'emoji'
  513. ) {
  514. const data = {
  515. from: 'customer',
  516. mode: 'text',
  517. date: new Date().getTime(),
  518. content: {
  519. text: filterXSS(res),
  520. },
  521. };
  522. formatInputCallBack && formatInputCallBack(data);
  523. res = '';
  524. }
  525. } else if (child.nodeName === 'BR') {
  526. res += '<br/>';
  527. } else if (child.nodeName === 'IMG') {
  528. // 有emjio 和 一般图片
  529. // 图片解析后直接发送,不跟文字表情一组
  530. if (child.name !== 'emoji') {
  531. let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;
  532. let src = child.outerHTML.match(srcReg);
  533. const data = {
  534. from: 'customer',
  535. mode: 'image',
  536. date: new Date().getTime(),
  537. content: {
  538. url: src[1],
  539. path: src[1].replace(/http:\/\/[^\/]*/, ''),
  540. },
  541. };
  542. formatInputCallBack && formatInputCallBack(data);
  543. } else {
  544. // 非表情图片跟文字一起发送
  545. res += child.outerHTML;
  546. }
  547. } else if (child.nodeName === 'DIV') {
  548. res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`;
  549. }
  550. });
  551. if (res) {
  552. const data = {
  553. from: 'customer',
  554. mode: 'text',
  555. date: new Date().getTime(),
  556. content: {
  557. text: filterXSS(res),
  558. },
  559. };
  560. formatInputCallBack && formatInputCallBack(data);
  561. }
  562. unref(virtualNode).innerHTML = '';
  563. };
  564. /**
  565. * 状态回调
  566. * @param {*} res -接口返回数据
  567. */
  568. const callBackNotice = (res) => {
  569. ElNotification({
  570. title: 'socket',
  571. message: res.msg,
  572. showClose: true,
  573. type: res.error === 0 ? 'success' : 'warning',
  574. duration: 1200,
  575. });
  576. };
  577. /**
  578. * 格式化发送信息
  579. * @param {Object} message
  580. * @returns obj - 消息对象
  581. */
  582. const formatInput = (message) => {
  583. let obj = {};
  584. switch (message.mode) {
  585. case 'text':
  586. obj = {
  587. message_type: 'text',
  588. message: message.content.text,
  589. };
  590. break;
  591. case 'image':
  592. obj = {
  593. message_type: 'image',
  594. message: message.content.path,
  595. };
  596. break;
  597. case 'goods':
  598. obj = {
  599. message_type: 'goods',
  600. message: message.content.item,
  601. };
  602. break;
  603. case 'order':
  604. obj = {
  605. message_type: 'order',
  606. message: message.content.item,
  607. };
  608. break;
  609. default:
  610. break;
  611. }
  612. return obj;
  613. };
  614. /**
  615. * 格式化接收信息
  616. * @param {*} message
  617. * @returns obj - 消息对象
  618. */
  619. const formatMessage = (message) => {
  620. let obj = {};
  621. switch (message.message_type) {
  622. case 'system':
  623. obj = {
  624. from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间
  625. mode: 'system', // goods,order,image,text,system
  626. date: message.create_time * 1000, //时间
  627. content: {
  628. //内容
  629. text: message.message,
  630. },
  631. };
  632. break;
  633. case 'text':
  634. obj = {
  635. from: message.sender_identify,
  636. mode: message.message_type,
  637. date: message.create_time * 1000, //时间
  638. sender: message.sender,
  639. content: {
  640. text: message.message,
  641. messageId: message.id,
  642. },
  643. };
  644. break;
  645. case 'image':
  646. obj = {
  647. from: message.sender_identify,
  648. mode: message.message_type,
  649. date: message.create_time * 1000, //时间
  650. sender: message.sender,
  651. content: {
  652. url: sheep.$url.cdn(message.message),
  653. messageId: message.id,
  654. },
  655. };
  656. break;
  657. case 'goods':
  658. obj = {
  659. from: message.sender_identify,
  660. mode: message.message_type,
  661. date: message.create_time * 1000, //时间
  662. sender: message.sender,
  663. content: {
  664. item: message.message,
  665. messageId: message.id,
  666. },
  667. };
  668. break;
  669. case 'order':
  670. obj = {
  671. from: message.sender_identify,
  672. mode: message.message_type,
  673. date: message.create_time * 1000, //时间
  674. sender: message.sender,
  675. content: {
  676. item: message.message,
  677. messageId: message.id,
  678. },
  679. };
  680. break;
  681. default:
  682. break;
  683. }
  684. return obj;
  685. };
  686. /**
  687. * file 转换为 img
  688. * @param {*} file - file 文件
  689. * @returns img - img标签
  690. */
  691. const readImg = (file) => {
  692. return new Promise((resolve, reject) => {
  693. const img = new Image();
  694. const reader = new FileReader();
  695. reader.onload = function (e) {
  696. img.src = e.target.result;
  697. };
  698. reader.onerror = function (e) {
  699. reject(e);
  700. };
  701. reader.readAsDataURL(file);
  702. img.onload = function () {
  703. resolve(img);
  704. };
  705. img.onerror = function (e) {
  706. reject(e);
  707. };
  708. });
  709. };
  710. /**
  711. * 压缩图片
  712. *@param img -被压缩的img对象
  713. * @param type -压缩后转换的文件类型
  714. * @param mx -触发压缩的图片最大宽度限制
  715. * @param mh -触发压缩的图片最大高度限制
  716. * @returns blob - 文件流
  717. */
  718. const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {
  719. return new Promise((resolve, reject) => {
  720. const canvas = document.createElement('canvas');
  721. const context = canvas.getContext('2d');
  722. const { width: originWidth, height: originHeight } = img;
  723. // 最大尺寸限制
  724. const maxWidth = mx;
  725. const maxHeight = mh;
  726. // 目标尺寸
  727. let targetWidth = originWidth;
  728. let targetHeight = originHeight;
  729. if (originWidth > maxWidth || originHeight > maxHeight) {
  730. if (originWidth / originHeight > 1) {
  731. // 宽图片
  732. targetWidth = maxWidth;
  733. targetHeight = Math.round(maxWidth * (originHeight / originWidth));
  734. } else {
  735. // 高图片
  736. targetHeight = maxHeight;
  737. targetWidth = Math.round(maxHeight * (originWidth / originHeight));
  738. }
  739. }
  740. canvas.width = targetWidth;
  741. canvas.height = targetHeight;
  742. context.clearRect(0, 0, targetWidth, targetHeight);
  743. // 图片绘制
  744. context.drawImage(img, 0, 0, targetWidth, targetHeight);
  745. canvas.toBlob(
  746. function (blob) {
  747. resolve(blob);
  748. },
  749. type,
  750. quality,
  751. );
  752. });
  753. };
  754. return {
  755. compressImg,
  756. readImg,
  757. formatMessage,
  758. formatInput,
  759. callBackNotice,
  760. socketInit,
  761. socketSendMsg,
  762. socketClose,
  763. socketHistoryList,
  764. getFocus,
  765. formatChatInput,
  766. onDrop,
  767. onPaste,
  768. upload,
  769. getUserToken,
  770. state,
  771. socketTest,
  772. showTime,
  773. formatTime,
  774. };
  775. }