socket.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  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. const res = await chat.unifiedToken();
  292. if (res.error === 0) {
  293. uni.setStorageSync('socketUserToken', res.data.token);
  294. // SocketIo && SocketIo.connected && socketLogin(res.data.token)
  295. }
  296. return res;
  297. };
  298. // 用户登录
  299. const socketLogin = (token) => {
  300. SocketIo.emit(
  301. 'login',
  302. {
  303. token: token,
  304. },
  305. (res) => {
  306. console.log(res, 'socket:login');
  307. state.customerUserInfo = res.data.chat_user;
  308. },
  309. );
  310. };
  311. // 顾客登录
  312. const socketCustomerLogin = (callBack) => {
  313. SocketIo.emit(
  314. 'customer_login',
  315. {
  316. room_id: state.chatConfig.room_id,
  317. },
  318. (res) => {
  319. state.templateChatList = res.data.questions.length ? res.data.questions : [];
  320. state.chatList.push({
  321. from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
  322. mode: 'template', // goods,order,image,text,system
  323. date: new Date().getTime(), //时间
  324. content: {
  325. //内容
  326. list: state.templateChatList,
  327. },
  328. });
  329. res.error === 0 && socketHistoryList(callBack);
  330. },
  331. );
  332. };
  333. // 获取历史消息
  334. const socketHistoryList = (historyCallBack) => {
  335. state.chatHistoryPagination.loadStatus = 'loading';
  336. state.chatHistoryPagination.page += 1;
  337. SocketIo.emit('messages', state.chatHistoryPagination, (res) => {
  338. if (res.error === 0) {
  339. state.chatHistoryPagination.total = res.data.messages.total;
  340. state.chatHistoryPagination.lastPage = res.data.messages.last_page;
  341. state.chatHistoryPagination.page = res.data.messages.current_page;
  342. res.data.messages.data.forEach((item) => {
  343. item.message_type && state.chatList.unshift(formatMessage(item));
  344. });
  345. state.chatHistoryPagination.loadStatus =
  346. state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage
  347. ? 'loadmore'
  348. : 'nomore';
  349. if (state.chatHistoryPagination.last_id == 0) {
  350. state.chatHistoryPagination.last_id = res.data.messages.data.length
  351. ? res.data.messages.data[0].id
  352. : 0;
  353. }
  354. state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();
  355. }
  356. // 历史记录之后,猜你想问
  357. // state.chatList.push({
  358. // from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
  359. // mode: 'template', // goods,order,image,text,system
  360. // date: new Date().getTime(), //时间
  361. // content: { //内容
  362. // list: state.templateChatList
  363. // }
  364. // })
  365. });
  366. };
  367. // 修改客服信息
  368. const editCustomerServerInfo = (data) => {
  369. state.customerServerInfo = {
  370. ...state.customerServerInfo,
  371. ...data,
  372. };
  373. };
  374. /**
  375. * ================
  376. * 工具函数 ↓
  377. * ===============
  378. */
  379. /**
  380. * 是否显示时间
  381. * @param {*} item - 数据
  382. * @param {*} index - 索引
  383. */
  384. const showTime = (item, index) => {
  385. if (unref(state.chatList)[index + 1]) {
  386. let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();
  387. if (dateString === dayjs(unref(item).date).fromNow()) {
  388. return false;
  389. } else {
  390. dateString = dayjs(unref(item).date).fromNow();
  391. return true;
  392. }
  393. }
  394. return false;
  395. };
  396. /**
  397. * 格式化时间
  398. * @param {*} time - 时间戳
  399. */
  400. const formatTime = (time) => {
  401. let diffTime = new Date().getTime() - time;
  402. if (diffTime > 28 * 24 * 60 * 1000) {
  403. return dayjs(time).format('MM/DD HH:mm');
  404. }
  405. if (diffTime > 360 * 28 * 24 * 60 * 1000) {
  406. return dayjs(time).format('YYYY/MM/DD HH:mm');
  407. }
  408. return dayjs(time).fromNow();
  409. };
  410. /**
  411. * 获取焦点
  412. * @param {*} virtualNode - 节点信息 ref
  413. */
  414. const getFocus = (virtualNode) => {
  415. if (window.getSelection) {
  416. let chatInput = unref(virtualNode);
  417. chatInput.focus();
  418. let range = window.getSelection();
  419. range.selectAllChildren(chatInput);
  420. range.collapseToEnd();
  421. } else if (document.selection) {
  422. let range = document.selection.createRange();
  423. range.moveToElementText(chatInput);
  424. range.collapse(false);
  425. range.select();
  426. }
  427. };
  428. /**
  429. * 文件上传
  430. * @param {Blob} file -文件数据流
  431. * @return {path,fullPath}
  432. */
  433. const upload = (name, file) => {
  434. return new Promise((resolve, reject) => {
  435. let data = new FormData();
  436. data.append('file', file, name);
  437. data.append('group', 'chat');
  438. ajax({
  439. url: '/upload',
  440. method: 'post',
  441. headers: {
  442. 'Content-Type': 'multipart/form-data',
  443. },
  444. data,
  445. success: function (res) {
  446. resolve(res);
  447. },
  448. error: function (err) {
  449. reject(err);
  450. },
  451. });
  452. });
  453. };
  454. /**
  455. * 粘贴到输入框
  456. * @param {*} e - 粘贴内容
  457. * @param {*} uploadHttp - 上传图片地址
  458. */
  459. const onPaste = async (e) => {
  460. let paste = e.clipboardData || window.clipboardData;
  461. let filesArr = Array.from(paste.files);
  462. filesArr.forEach(async (child) => {
  463. if (child && child.type.includes('image')) {
  464. e.preventDefault(); //阻止默认
  465. let file = child;
  466. const img = await readImg(file);
  467. const blob = await compressImg(img, file.type);
  468. const { data } = await upload(file.name, blob);
  469. let image = `<img class="full-url" src='${data.fullurl}'>`;
  470. document.execCommand('insertHTML', false, image);
  471. } else {
  472. document.execCommand('insertHTML', false, paste.getData('text'));
  473. }
  474. });
  475. };
  476. /**
  477. * 拖拽到输入框
  478. * @param {*} e - 粘贴内容
  479. * @param {*} uploadHttp - 上传图片地址
  480. */
  481. const onDrop = async (e) => {
  482. e.preventDefault(); //阻止默认
  483. let filesArr = Array.from(e.dataTransfer.files);
  484. filesArr.forEach(async (child) => {
  485. if (child && child.type.includes('image')) {
  486. let file = child;
  487. const img = await readImg(file);
  488. const blob = await compressImg(img, file.type);
  489. const { data } = await upload(file.name, blob);
  490. let image = `<img class="full-url" src='${data.fullurl}' >`;
  491. document.execCommand('insertHTML', false, image);
  492. } else {
  493. ElMessage({
  494. message: '禁止拖拽非图片资源',
  495. type: 'warning',
  496. });
  497. }
  498. });
  499. };
  500. /**
  501. * 解析富文本输入框内容
  502. * @param {*} virtualNode -节点信息
  503. * @param {Function} formatInputCallBack - cb 回调
  504. */
  505. const formatChatInput = (virtualNode, formatInputCallBack) => {
  506. let res = '';
  507. let elemArr = Array.from(virtualNode.childNodes);
  508. elemArr.forEach((child, index) => {
  509. if (child.nodeName === '#text') {
  510. //如果为文本节点
  511. res += child.nodeValue;
  512. if (
  513. //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。
  514. elemArr[index + 1] &&
  515. elemArr[index + 1].nodeName === 'IMG' &&
  516. elemArr[index + 1] &&
  517. elemArr[index + 1].name !== 'emoji'
  518. ) {
  519. const data = {
  520. from: 'customer',
  521. mode: 'text',
  522. date: new Date().getTime(),
  523. content: {
  524. text: filterXSS(res),
  525. },
  526. };
  527. formatInputCallBack && formatInputCallBack(data);
  528. res = '';
  529. }
  530. } else if (child.nodeName === 'BR') {
  531. res += '<br/>';
  532. } else if (child.nodeName === 'IMG') {
  533. // 有emjio 和 一般图片
  534. // 图片解析后直接发送,不跟文字表情一组
  535. if (child.name !== 'emoji') {
  536. let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;
  537. let src = child.outerHTML.match(srcReg);
  538. const data = {
  539. from: 'customer',
  540. mode: 'image',
  541. date: new Date().getTime(),
  542. content: {
  543. url: src[1],
  544. path: src[1].replace(/http:\/\/[^\/]*/, ''),
  545. },
  546. };
  547. formatInputCallBack && formatInputCallBack(data);
  548. } else {
  549. // 非表情图片跟文字一起发送
  550. res += child.outerHTML;
  551. }
  552. } else if (child.nodeName === 'DIV') {
  553. res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`;
  554. }
  555. });
  556. if (res) {
  557. const data = {
  558. from: 'customer',
  559. mode: 'text',
  560. date: new Date().getTime(),
  561. content: {
  562. text: filterXSS(res),
  563. },
  564. };
  565. formatInputCallBack && formatInputCallBack(data);
  566. }
  567. unref(virtualNode).innerHTML = '';
  568. };
  569. /**
  570. * 状态回调
  571. * @param {*} res -接口返回数据
  572. */
  573. const callBackNotice = (res) => {
  574. ElNotification({
  575. title: 'socket',
  576. message: res.msg,
  577. showClose: true,
  578. type: res.error === 0 ? 'success' : 'warning',
  579. duration: 1200,
  580. });
  581. };
  582. /**
  583. * 格式化发送信息
  584. * @param {Object} message
  585. * @returns obj - 消息对象
  586. */
  587. const formatInput = (message) => {
  588. let obj = {};
  589. switch (message.mode) {
  590. case 'text':
  591. obj = {
  592. message_type: 'text',
  593. message: message.content.text,
  594. };
  595. break;
  596. case 'image':
  597. obj = {
  598. message_type: 'image',
  599. message: message.content.path,
  600. };
  601. break;
  602. case 'goods':
  603. obj = {
  604. message_type: 'goods',
  605. message: message.content.item,
  606. };
  607. break;
  608. case 'order':
  609. obj = {
  610. message_type: 'order',
  611. message: message.content.item,
  612. };
  613. break;
  614. default:
  615. break;
  616. }
  617. return obj;
  618. };
  619. /**
  620. * 格式化接收信息
  621. * @param {*} message
  622. * @returns obj - 消息对象
  623. */
  624. const formatMessage = (message) => {
  625. let obj = {};
  626. switch (message.message_type) {
  627. case 'system':
  628. obj = {
  629. from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间
  630. mode: 'system', // goods,order,image,text,system
  631. date: message.create_time * 1000, //时间
  632. content: {
  633. //内容
  634. text: message.message,
  635. },
  636. };
  637. break;
  638. case 'text':
  639. obj = {
  640. from: message.sender_identify,
  641. mode: message.message_type,
  642. date: message.create_time * 1000, //时间
  643. sender: message.sender,
  644. content: {
  645. text: message.message,
  646. messageId: message.id,
  647. },
  648. };
  649. break;
  650. case 'image':
  651. obj = {
  652. from: message.sender_identify,
  653. mode: message.message_type,
  654. date: message.create_time * 1000, //时间
  655. sender: message.sender,
  656. content: {
  657. url: sheep.$url.cdn(message.message),
  658. messageId: message.id,
  659. },
  660. };
  661. break;
  662. case 'goods':
  663. obj = {
  664. from: message.sender_identify,
  665. mode: message.message_type,
  666. date: message.create_time * 1000, //时间
  667. sender: message.sender,
  668. content: {
  669. item: message.message,
  670. messageId: message.id,
  671. },
  672. };
  673. break;
  674. case 'order':
  675. obj = {
  676. from: message.sender_identify,
  677. mode: message.message_type,
  678. date: message.create_time * 1000, //时间
  679. sender: message.sender,
  680. content: {
  681. item: message.message,
  682. messageId: message.id,
  683. },
  684. };
  685. break;
  686. default:
  687. break;
  688. }
  689. return obj;
  690. };
  691. /**
  692. * file 转换为 img
  693. * @param {*} file - file 文件
  694. * @returns img - img标签
  695. */
  696. const readImg = (file) => {
  697. return new Promise((resolve, reject) => {
  698. const img = new Image();
  699. const reader = new FileReader();
  700. reader.onload = function (e) {
  701. img.src = e.target.result;
  702. };
  703. reader.onerror = function (e) {
  704. reject(e);
  705. };
  706. reader.readAsDataURL(file);
  707. img.onload = function () {
  708. resolve(img);
  709. };
  710. img.onerror = function (e) {
  711. reject(e);
  712. };
  713. });
  714. };
  715. /**
  716. * 压缩图片
  717. *@param img -被压缩的img对象
  718. * @param type -压缩后转换的文件类型
  719. * @param mx -触发压缩的图片最大宽度限制
  720. * @param mh -触发压缩的图片最大高度限制
  721. * @returns blob - 文件流
  722. */
  723. const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {
  724. return new Promise((resolve, reject) => {
  725. const canvas = document.createElement('canvas');
  726. const context = canvas.getContext('2d');
  727. const { width: originWidth, height: originHeight } = img;
  728. // 最大尺寸限制
  729. const maxWidth = mx;
  730. const maxHeight = mh;
  731. // 目标尺寸
  732. let targetWidth = originWidth;
  733. let targetHeight = originHeight;
  734. if (originWidth > maxWidth || originHeight > maxHeight) {
  735. if (originWidth / originHeight > 1) {
  736. // 宽图片
  737. targetWidth = maxWidth;
  738. targetHeight = Math.round(maxWidth * (originHeight / originWidth));
  739. } else {
  740. // 高图片
  741. targetHeight = maxHeight;
  742. targetWidth = Math.round(maxHeight * (originWidth / originHeight));
  743. }
  744. }
  745. canvas.width = targetWidth;
  746. canvas.height = targetHeight;
  747. context.clearRect(0, 0, targetWidth, targetHeight);
  748. // 图片绘制
  749. context.drawImage(img, 0, 0, targetWidth, targetHeight);
  750. canvas.toBlob(
  751. function (blob) {
  752. resolve(blob);
  753. },
  754. type,
  755. quality,
  756. );
  757. });
  758. };
  759. return {
  760. compressImg,
  761. readImg,
  762. formatMessage,
  763. formatInput,
  764. callBackNotice,
  765. socketInit,
  766. socketSendMsg,
  767. socketClose,
  768. socketHistoryList,
  769. getFocus,
  770. formatChatInput,
  771. onDrop,
  772. onPaste,
  773. upload,
  774. getUserToken,
  775. state,
  776. socketTest,
  777. showTime,
  778. formatTime,
  779. };
  780. }