| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 | import { reactive, ref, unref } from 'vue';import sheep from '@/sheep';import chat from '@/sheep/api/chat';import dayjs from 'dayjs';import io from '@hyoga/uni-socket.io';export function useChatWebSocket(socketConfig) {  let SocketIo = null;  // chat状态数据  const state = reactive({    chatDotNum: 0, //总状态红点    chatList: [], //会话信息    customerUserInfo: {}, //用户信息    customerServerInfo: {      //客服信息      title: '连接中...',      state: 'connecting',      avatar: null,      nickname: '',    },    socketState: {      isConnect: true, //是否连接成功      isConnecting: false, //重连中,不允许新的socket开启。      tip: '',    },    chatHistoryPagination: {      page: 0, //当前页      list_rows: 10, //每页条数      last_id: 0, //最后条ID      lastPage: 0, //总共多少页      loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态    },    templateChatList: [], //猜你想问    chatConfig: {}, // 配置信息    isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败  });  /**   * 连接初始化   * @param {Object} config  - 配置信息   * @param {Function} callBack -回调函数,有新消息接入,保持底部   */  const socketInit = (config, callBack) => {    state.chatConfig = config;    if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false    if (state.socketState.isConnecting) return; // 重连中,返回false    // 启动初始化    SocketIo = io(config.chat_domain, {      reconnection: true, // 默认 true    是否断线重连      reconnectionAttempts: 5, // 默认无限次   断线尝试次数      reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。      reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000      randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间      timeout: 20000, // 默认 20s      transports: ['websocket', 'polling'], // websocket | polling,      ...config,    });    // 监听连接    SocketIo.on('connect', async (res) => {      socketReset(callBack);      // socket连接      // 用户登录      // 顾客登录      console.log('socket:connect');    });    // 监听消息    SocketIo.on('message', (res) => {      if (res.error === 0) {        const { message, sender } = res.data;        state.chatList.push(formatMessage(res.data.message));        // 告诉父级页面        // window.parent.postMessage({        // 	chatDotNum: ++state.chatDotNum        // })        callBack && callBack();      }    });    // 监听客服接入成功    SocketIo.on('customer_service_access', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: res.data.customer_service.name,          state: 'online',          avatar: res.data.customer_service.avatar,        });        state.chatList.push(formatMessage(res.data.message));        // callBack && callBack()      }    });    // 监听排队等待    SocketIo.on('waiting_queue', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: res.data.title,          state: 'waiting',          avatar: '',        });        // callBack && callBack()      }    });    // 监听没有客服在线    SocketIo.on('no_customer_service', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: '暂无客服在线...',          state: 'waiting',          avatar: '',        });      }      state.chatList.push(formatMessage(res.data.message));      // callBack && callBack()    });    // 监听客服上线    SocketIo.on('customer_service_online', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: res.data.customer_service.name,          state: 'online',          avatar: res.data.customer_service.avatar,        });      }    });    // 监听客服下线    SocketIo.on('customer_service_offline', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: res.data.customer_service.name,          state: 'offline',          avatar: res.data.customer_service.avatar,        });      }    });    // 监听客服忙碌    SocketIo.on('customer_service_busy', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: res.data.customer_service.name,          state: 'busy',          avatar: res.data.customer_service.avatar,        });      }    });    // 监听客服断开链接    SocketIo.on('customer_service_break', (res) => {      if (res.error === 0) {        editCustomerServerInfo({          title: '客服服务结束',          state: 'offline',          avatar: '',        });        state.socketState.isConnect = false;        state.socketState.tip = '当前服务已结束';      }      state.chatList.push(formatMessage(res.data.message));      // callBack && callBack()    });    // 监听自定义错误 custom_error    SocketIo.on('custom_error', (error) => {      editCustomerServerInfo({        title: error.msg,        state: 'offline',        avatar: '',      });      console.log('custom_error:', error);    });    // 监听错误 error    SocketIo.on('error', (error) => {      console.log('error:', error);    });    // 重连失败 connect_error    SocketIo.on('connect_error', (error) => {      console.log('connect_error');    });    // 连接上,但无反应 connect_timeout    SocketIo.on('connect_timeout', (error) => {      console.log(error, 'connect_timeout');    });    // 服务进程销毁 disconnect    SocketIo.on('disconnect', (error) => {      console.log(error, 'disconnect');    });    // 服务重启重连上reconnect    SocketIo.on('reconnect', (error) => {      console.log(error, 'reconnect');    });    // 开始重连reconnect_attempt    SocketIo.on('reconnect_attempt', (error) => {      state.socketState.isConnect = false;      state.socketState.isConnecting = true;      editCustomerServerInfo({        title: `重连中,第${error}次尝试...`,        state: 'waiting',        avatar: '',      });      console.log(error, 'reconnect_attempt');    });    // 重新连接中reconnecting    SocketIo.on('reconnecting', (error) => {      console.log(error, 'reconnecting');    });    // 重新连接错误reconnect_error    SocketIo.on('reconnect_error', (error) => {      console.log('reconnect_error');    });    // 重新连接失败reconnect_failed    SocketIo.on('reconnect_failed', (error) => {      state.socketState.isConnecting = false;      editCustomerServerInfo({        title: `重连失败,请刷新重试~`,        state: 'waiting',        avatar: '',      });      console.log(error, 'reconnect_failed');      // setTimeout(() => {      state.isSendSucces = 1;      // }, 500)    });  };  // 重置socket  const socketReset = (callBack) => {    state.chatList = [];    state.chatHistoryList = [];    state.chatHistoryPagination = {      page: 0,      per_page: 10,      last_id: 0,      totalPage: 0,    };    socketConnection(callBack); // 连接  };  // 退出连接  const socketClose = () => {    SocketIo.emit('customer_logout', {}, (res) => {      console.log('socket:退出', res);    });  };  // 测试事件  const socketTest = () => {    SocketIo.emit('test', {}, (res) => {      console.log('test:test', res);    });  };  // 发送消息  const socketSendMsg = (data, sendMsgCallBack) => {    state.isSendSucces = -1;    state.chatList.push(data);    sendMsgCallBack && sendMsgCallBack();    SocketIo.emit(      'message',      {        message: formatInput(data),        ...data.customData,      },      (res) => {        // setTimeout(() => {        state.isSendSucces = res.error;        // }, 500)        // console.log(res, 'socket:send');        // sendMsgCallBack && sendMsgCallBack()      },    );  };  // 连接socket,存入sessionId  const socketConnection = (callBack) => {    SocketIo.emit(      'connection',      {        auth: 'user',        token: uni.getStorageSync('socketUserToken') || '',        session_id: uni.getStorageSync('socketSessionId') || '',      },      (res) => {        if (res.error === 0) {          socketCustomerLogin(callBack);          uni.setStorageSync('socketSessionId', res.data.session_id);          // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(          // 	'socketUserToken')) // 如果有用户token,绑定          state.customerUserInfo = res.data.chat_user;          state.socketState.isConnect = true;        } else {          editCustomerServerInfo({            title: `服务器异常!`,            state: 'waiting',            avatar: '',          });          state.socketState.isConnect = false;        }      },    );  };  // 用户id,获取token  const getUserToken = async (id) => {    const res = await chat.unifiedToken();    if (res.error === 0) {      uni.setStorageSync('socketUserToken', res.data.token);      // SocketIo && SocketIo.connected && socketLogin(res.data.token)    }    return res;  };  // 用户登录  const socketLogin = (token) => {    SocketIo.emit(      'login',      {        token: token,      },      (res) => {        console.log(res, 'socket:login');        state.customerUserInfo = res.data.chat_user;      },    );  };  // 顾客登录  const socketCustomerLogin = (callBack) => {    SocketIo.emit(      'customer_login',      {        room_id: state.chatConfig.room_id,      },      (res) => {        state.templateChatList = res.data.questions.length ? res.data.questions : [];        state.chatList.push({          from: 'customer_service', // 用户customer右 |  顾客customer_service左 | 系统system中间          mode: 'template', // goods,order,image,text,system          date: new Date().getTime(), //时间          content: {            //内容            list: state.templateChatList,          },        });        res.error === 0 && socketHistoryList(callBack);      },    );  };  // 获取历史消息  const socketHistoryList = (historyCallBack) => {    state.chatHistoryPagination.loadStatus = 'loading';    state.chatHistoryPagination.page += 1;    SocketIo.emit('messages', state.chatHistoryPagination, (res) => {      if (res.error === 0) {        state.chatHistoryPagination.total = res.data.messages.total;        state.chatHistoryPagination.lastPage = res.data.messages.last_page;        state.chatHistoryPagination.page = res.data.messages.current_page;        res.data.messages.data.forEach((item) => {          item.message_type && state.chatList.unshift(formatMessage(item));        });        state.chatHistoryPagination.loadStatus =          state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage            ? 'loadmore'            : 'nomore';        if (state.chatHistoryPagination.last_id == 0) {          state.chatHistoryPagination.last_id = res.data.messages.data.length            ? res.data.messages.data[0].id            : 0;        }        state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();      }      // 历史记录之后,猜你想问      // state.chatList.push({      // 	from: 'customer_service', // 用户customer右 |  顾客customer_service左 | 系统system中间      // 	mode: 'template', // goods,order,image,text,system      // 	date: new Date().getTime(), //时间      // 	content: { //内容      // 		list: state.templateChatList      // 	}      // })    });  };  // 修改客服信息  const editCustomerServerInfo = (data) => {    state.customerServerInfo = {      ...state.customerServerInfo,      ...data,    };  };  /**   * ================   * 工具函数 ↓   * ===============   */  /**   * 是否显示时间   * @param {*} item - 数据   * @param {*} index - 索引   */  const showTime = (item, index) => {    if (unref(state.chatList)[index + 1]) {      let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();      if (dateString === dayjs(unref(item).date).fromNow()) {        return false;      } else {        dateString = dayjs(unref(item).date).fromNow();        return true;      }    }    return false;  };  /**   * 格式化时间   * @param {*} time - 时间戳   */  const formatTime = (time) => {    let diffTime = new Date().getTime() - time;    if (diffTime > 28 * 24 * 60 * 1000) {      return dayjs(time).format('MM/DD HH:mm');    }    if (diffTime > 360 * 28 * 24 * 60 * 1000) {      return dayjs(time).format('YYYY/MM/DD HH:mm');    }    return dayjs(time).fromNow();  };  /**   * 获取焦点   * @param {*} virtualNode - 节点信息 ref   */  const getFocus = (virtualNode) => {    if (window.getSelection) {      let chatInput = unref(virtualNode);      chatInput.focus();      let range = window.getSelection();      range.selectAllChildren(chatInput);      range.collapseToEnd();    } else if (document.selection) {      let range = document.selection.createRange();      range.moveToElementText(chatInput);      range.collapse(false);      range.select();    }  };  /**   * 文件上传   * @param {Blob} file -文件数据流   * @return {path,fullPath}   */  const upload = (name, file) => {    return new Promise((resolve, reject) => {      let data = new FormData();      data.append('file', file, name);      data.append('group', 'chat');      ajax({        url: '/upload',        method: 'post',        headers: {          'Content-Type': 'multipart/form-data',        },        data,        success: function (res) {          resolve(res);        },        error: function (err) {          reject(err);        },      });    });  };  /**   * 粘贴到输入框   * @param {*} e  - 粘贴内容   * @param {*} uploadHttp - 上传图片地址   */  const onPaste = async (e) => {    let paste = e.clipboardData || window.clipboardData;    let filesArr = Array.from(paste.files);    filesArr.forEach(async (child) => {      if (child && child.type.includes('image')) {        e.preventDefault(); //阻止默认        let file = child;        const img = await readImg(file);        const blob = await compressImg(img, file.type);        const { data } = await upload(file.name, blob);        let image = `<img src='${data.fullurl}'  style='object-fit: cover;width:100px;height:100px;border-radius:6px'>`;        document.execCommand('insertHTML', false, image);      } else {        document.execCommand('insertHTML', false, paste.getData('text'));      }    });  };  /**   * 拖拽到输入框   * @param {*} e  - 粘贴内容   * @param {*} uploadHttp - 上传图片地址   */  const onDrop = async (e) => {    e.preventDefault(); //阻止默认    let filesArr = Array.from(e.dataTransfer.files);    filesArr.forEach(async (child) => {      if (child && child.type.includes('image')) {        let file = child;        const img = await readImg(file);        const blob = await compressImg(img, file.type);        const { data } = await upload(file.name, blob);        let image = `<img src='${data.fullurl}'   style='object-fit: cover;width:100px;height:100px;border-radius:6px'>`;        document.execCommand('insertHTML', false, image);      } else {        ElMessage({          message: '禁止拖拽非图片资源',          type: 'warning',        });      }    });  };  /**   * 解析富文本输入框内容   * @param {*}  virtualNode -节点信息   * @param {Function} formatInputCallBack - cb 回调   */  const formatChatInput = (virtualNode, formatInputCallBack) => {    let res = '';    let elemArr = Array.from(virtualNode.childNodes);    elemArr.forEach((child, index) => {      if (child.nodeName === '#text') {        //如果为文本节点        res += child.nodeValue;        if (          //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。          elemArr[index + 1] &&          elemArr[index + 1].nodeName === 'IMG' &&          elemArr[index + 1] &&          elemArr[index + 1].name !== 'emoji'        ) {          const data = {            from: 'customer',            mode: 'text',            date: new Date().getTime(),            content: {              text: filterXSS(res),            },          };          formatInputCallBack && formatInputCallBack(data);          res = '';        }      } else if (child.nodeName === 'BR') {        res += '<br/>';      } else if (child.nodeName === 'IMG') {        // 有emjio 和 一般图片        // 图片解析后直接发送,不跟文字表情一组        if (child.name !== 'emoji') {          let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;          let src = child.outerHTML.match(srcReg);          const data = {            from: 'customer',            mode: 'image',            date: new Date().getTime(),            content: {              url: src[1],              path: src[1].replace(/http:\/\/[^\/]*/, ''),            },          };          formatInputCallBack && formatInputCallBack(data);        } else {          // 非表情图片跟文字一起发送          res += child.outerHTML;        }      } else if (child.nodeName === 'DIV') {        res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`;      }    });    if (res) {      const data = {        from: 'customer',        mode: 'text',        date: new Date().getTime(),        content: {          text: filterXSS(res),        },      };      formatInputCallBack && formatInputCallBack(data);    }    unref(virtualNode).innerHTML = '';  };  /**   * 状态回调   * @param {*} res -接口返回数据   */  const callBackNotice = (res) => {    ElNotification({      title: 'socket',      message: res.msg,      showClose: true,      type: res.error === 0 ? 'success' : 'warning',      duration: 1200,    });  };  /**   * 格式化发送信息   * @param {Object} message   * @returns  obj - 消息对象   */  const formatInput = (message) => {    let obj = {};    switch (message.mode) {      case 'text':        obj = {          message_type: 'text',          message: message.content.text,        };        break;      case 'image':        obj = {          message_type: 'image',          message: message.content.path,        };        break;      case 'goods':        obj = {          message_type: 'goods',          message: message.content.item,        };        break;      case 'order':        obj = {          message_type: 'order',          message: message.content.item,        };        break;      default:        break;    }    return obj;  };  /**   * 格式化接收信息   * @param {*} message   * @returns obj - 消息对象   */  const formatMessage = (message) => {    let obj = {};    switch (message.message_type) {      case 'system':        obj = {          from: 'system', // 用户customer左 |  顾客customer_service右 | 系统system中间          mode: 'system', // goods,order,image,text,system          date: message.create_time * 1000, //时间          content: {            //内容            text: message.message,          },        };        break;      case 'text':        obj = {          from: message.sender_identify,          mode: message.message_type,          date: message.create_time * 1000, //时间          sender: message.sender,          content: {            text: message.message,            messageId: message.id,          },        };        break;      case 'image':        obj = {          from: message.sender_identify,          mode: message.message_type,          date: message.create_time * 1000, //时间          sender: message.sender,          content: {            url: sheep.$url.cdn(message.message),            messageId: message.id,          },        };        break;      case 'goods':        obj = {          from: message.sender_identify,          mode: message.message_type,          date: message.create_time * 1000, //时间          sender: message.sender,          content: {            item: message.message,            messageId: message.id,          },        };        break;      case 'order':        obj = {          from: message.sender_identify,          mode: message.message_type,          date: message.create_time * 1000, //时间          sender: message.sender,          content: {            item: message.message,            messageId: message.id,          },        };        break;      default:        break;    }    return obj;  };  /**   * file 转换为 img   * @param {*} file  - file 文件   * @returns  img   - img标签   */  const readImg = (file) => {    return new Promise((resolve, reject) => {      const img = new Image();      const reader = new FileReader();      reader.onload = function (e) {        img.src = e.target.result;      };      reader.onerror = function (e) {        reject(e);      };      reader.readAsDataURL(file);      img.onload = function () {        resolve(img);      };      img.onerror = function (e) {        reject(e);      };    });  };  /**   * 压缩图片   *@param img -被压缩的img对象   * @param type -压缩后转换的文件类型   * @param mx -触发压缩的图片最大宽度限制   * @param mh -触发压缩的图片最大高度限制   * @returns blob - 文件流   */  const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {    return new Promise((resolve, reject) => {      const canvas = document.createElement('canvas');      const context = canvas.getContext('2d');      const { width: originWidth, height: originHeight } = img;      // 最大尺寸限制      const maxWidth = mx;      const maxHeight = mh;      // 目标尺寸      let targetWidth = originWidth;      let targetHeight = originHeight;      if (originWidth > maxWidth || originHeight > maxHeight) {        if (originWidth / originHeight > 1) {          // 宽图片          targetWidth = maxWidth;          targetHeight = Math.round(maxWidth * (originHeight / originWidth));        } else {          // 高图片          targetHeight = maxHeight;          targetWidth = Math.round(maxHeight * (originWidth / originHeight));        }      }      canvas.width = targetWidth;      canvas.height = targetHeight;      context.clearRect(0, 0, targetWidth, targetHeight);      // 图片绘制      context.drawImage(img, 0, 0, targetWidth, targetHeight);      canvas.toBlob(        function (blob) {          resolve(blob);        },        type,        quality,      );    });  };  return {    compressImg,    readImg,    formatMessage,    formatInput,    callBackNotice,    socketInit,    socketSendMsg,    socketClose,    socketHistoryList,    getFocus,    formatChatInput,    onDrop,    onPaste,    upload,    getUserToken,    state,    socketTest,    showTime,    formatTime,  };}
 |