react客服

index.tsx
import { useEffect, useRef, useState } from 'react';
import './index.less';
import { membersList, messageList } from '@/services/info/info-api';
import { Badge, message } from 'antd';
import { GbDivider } from '@/components';
import { LeftOutlined } from '@ant-design/icons';
import { useLocation } from 'react-router-dom';
import MainChatting from './main-chatting';
import FooterPage from './footer-page';
//客服聊天
export default function Info() {
  const [value, setValue] = useState('');
  const [fileImg, setFileImg] = useState<string>('');
  const useLocationTo = useLocation();
  console.log('useLocationTo', useLocationTo);
  const [membersLeft, setMembersLeft] = useState<any[]>([]);
  const [messageRight, setMessageRight] = useState<any[]>([]);
  const [sendFunc, setSendFunc] = useState<any>();
  const [dataList, setDataList] = useState<any>();
  const mainRef = useRef<any>();
  let ws: any = null;
  let list = localStorage.getItem('rodledata')
    ? JSON.parse(localStorage.getItem('rodledata') as string)
    : [];
  const userName = list?.loginName;
  const connectWebSocket = () => {
    if ('WebSocket' in window) {
      ws = new WebSocket(`ws://xxx/${userName}`);
      setSendFunc(ws);
      ws.onopen = () => {
        console.log('服务器已连接');
      };
      ws.onmessage = (msg: any) => {
        leftMembersList();
        console.log('来自服务器端的数据:' + dataList?.msg, msg.data); //监听接受来自服务端的信息
      };
      ws.onclose = () => {
        console.log('服务器关闭');
      };
    } else {
      alert('当前浏览器不支持Websocket');
    }
  };
  const sendserver = (type: string) => {
    const socketMsg = {
      msg: type === 'text' ? value : fileImg,
      typeMsg: type,
      toUser: dataList?.senderName,
      showName: dataList?.showName,
      type: dataList?.type,
      fromUser: dataList?.userName,
      userId: dataList?.userId,
      senderId: dataList?.senderId,
      userUrl: list?.userAvatar
      // ? list?.userAvatar
      // : 'http://frphn2.chickfrp.com:10029/fisp-image/2023/07/18/341266221211459c96ec430bc01f2853/logos.png"'
    };
    console.log('socketMsg', socketMsg);
    //
    if (value || fileImg) {
      sendFunc.send(JSON.stringify(socketMsg));
      setValue('');
    } else {
      message.success('请输入内容');
    }
  };

  const leftMembersList = async () => {
    const data: any = {
      username: userName,
      orderId: useLocationTo?.state?.orderId
    };
    await membersList(data).then((res) => {
      console.log('fgggggggg', res);
      setMembersLeft(res.data);
      const messagedata: any = {
        socketMsg: {
          type: res?.data[0]?.type,
          toUser: res?.data[0]?.userName,
          fromUser: res?.data[0]?.senderName,
          msg: '',
          senderId: res?.data[0]?.senderId,
          userId: res?.data[0]?.userId,
          userUrl: res?.data[0]?.senderUrl
        }
      };
      setDataList(res?.data[0]);
      console.log('res?.data[0]', res?.data[0]);

      console.log('df', res?.data[0]);
      if (res?.data?.length > 0) {
        getNowMessageList(messagedata);
      }
      connectWebSocket();
    });
  };
  const handleRef = () => {
    mainRef.current?.scrollIntoView({ behavior: 'auto' });
  };
  useEffect(() => {
    handleRef();
  }, [messageRight]);
  const getNowMessageList = async (data: any) => {
    await messageList(data).then((res: any) => {
      if (res.success) {
        console.log('右边信息', res.data);
        setMessageRight(res.data);
      }
    });
  };
  useEffect(() => {
    leftMembersList();
  }, []);
  return (
    <div className="in-wrap">
      <div style={{ cursor: 'pointer' }} className={'edit-top'}>
        <LeftOutlined
          onClick={() => {
            window.history.back();
          }}
          style={{ padding: '15px 5px 15px 15px' }}
        />
        <p style={{ margin: 0, padding: '10px 5px 10px 0px', display: 'inline-block' }}>客服</p>
        <GbDivider />
      </div>
      <div className="info-page">
        <div className="info-right">
          {membersLeft?.map((item, index) => {
            return (
              <div className="info-right-header" key={index}>
                <Badge count={item.count}>{item.showName}</Badge>
              </div>
            );
          })}
          <div className="info-right-main">
            {messageRight?.map((i, v) => {
              return (
                <div
                  key={v}
                  className="main-wrap"
                  ref={mainRef}
                  style={{ textAlign: userName === i.fromName ? 'right' : 'left' }}
                >
                  {/* 聊天框 */}
                  <MainChatting
                    data={{
                      typeMsg: i.typeMsg,
                      topsubtitle: `${i?.fromName} ${i?.createTime}`,
                      userName: userName,
                      fromName: i?.fromName,
                      message: i?.message,
                      userUrl: i.userUrl
                    }}
                  />
                </div>
              );
            })}
          </div>
          <div className="info-right-footer">
            <FooterPage
              data={{
                setValue: setValue,
                setFileImg: setFileImg,
                sendserver: sendserver,
                value: value
              }}
            ></FooterPage>
          </div>
        </div>
      </div>
    </div>
  );
}

  

main-chatting.tsx
import React from 'react';
import textImg from '../../../assets/wjj2.png';
import { Image } from 'antd';
export default function MainChatting(props: {
  data: {
    typeMsg: string;
    topsubtitle: any;
    userName: string;
    fromName: string;
    message: string;
    userUrl: string;
  };
}) {
  const { data } = props;
  const handletype = (type: string, dri: string) => {
    if (type == 'text') {
      return (
        <>
          {dri === 'right' ? (
            <div className="info-img-right" style={{ marginLeft: 'auto' }}>
              {data?.message}
            </div>
          ) : (
            <div className="info-img-left">{data?.message}</div>
          )}
        </>
      );
    } else if (type == 'img') {
      return (
        <>
          {dri === 'right' ? (
            <div className="main-img-right" style={{ marginLeft: 'auto' }}>
              <Image src={data?.message} alt="" />
            </div>
          ) : (
            <div className="main-img-left">
              <Image src={data?.message} alt="" />
            </div>
          )}
        </>
      );
    } else if (type == 'file') {
      return (
        <>
          {dri === 'right' ? (
            <div className="main-file-right" style={{ marginLeft: 'auto' }}>
              <span>
                {
                  data?.message?.split('/').slice(-1)[
                    data?.message?.split('/').slice(-1).length - 1
                  ]
                }
              </span>
              <a target="_blank" href={data?.message} download>
                <img
                  style={{ width: '25px', objectFit: 'contain', marginLeft: '10px' }}
                  src={textImg}
                  alt=""
                />
              </a>
            </div>
          ) : (
            <div className="main-file-left">
              <span>
                {
                  data?.message?.split('/').slice(-1)[
                    data?.message?.split('/').slice(-1).length - 1
                  ]
                }
              </span>
              <a target="_blank" href={data?.message} download>
                <img
                  style={{ width: '25px', objectFit: 'contain', marginLeft: '10px' }}
                  src={textImg}
                  alt=""
                />
              </a>
            </div>
          )}
        </>
      );
    }
  };
  return (
    <div className="main-card">
      <p>{data?.topsubtitle}</p>
      <div className="info-img">
        {data?.userName === data?.fromName ? (
          <>
            {handletype(data?.typeMsg, 'right')}
            <img style={{ marginLeft: '10px' }} src={data?.userUrl} alt="" />
          </>
        ) : (
          <>
            <img style={{ marginRight: '10px' }} src={data?.userUrl} alt="" />
            {handletype(data?.typeMsg, 'left')}
          </>
        )}
      </div>
    </div>
  );
}

  

footer-page.tsx
import { Webmsg } from '@/services/info/info-api';
import { Button, Popover, Upload, message } from 'antd';
import TextArea from 'antd/lib/input/TextArea';
import React, { useEffect, useState } from 'react';
const baseUrl = import.meta.env.VITE_APP_PROXY_URL;
import type { UploadProps } from 'antd/es/upload/interface';
export default function FooterPage(props: {
  data: {
    setValue: any;
    setFileImg: any;
    sendserver: any;
    value: any;
  };
}) {
  const { data } = props;
  const [open, setOpen] = useState(false);
  const [look, setLook] = useState([]);
  const handleWebmsg = async () => {
    await Webmsg().then((res) => {
      console.log('表情', res?.data);
      setLook(res?.data);
    });
  };
  const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    console.log('Change:', e.target.value);
    data?.setValue(e.target.value);
  };
  const sendImg = (remove: any, file: any) => {
    if (file?.type?.indexOf('image/') === 0) {
      data?.sendserver('img');
    } else if (file?.type?.indexOf('application/') === 0) {
      data?.sendserver('file');
    }
    remove(file);
  };
  const cancelImg = (remove: any, file: any) => {
    console.log(remove);
    remove(file);
  };
  const hide = (i: string) => {
    data?.setValue(i);
    setOpen(false);
  };
  const handleOpenChange = (newOpen: boolean) => {
    setOpen(newOpen);
  };
  const propsUpload: UploadProps = {
    maxCount: 1,
    name: 'file',
    action: `${baseUrl}electronic_seal/upload`,
    headers: {
      Authorization: unescape(localStorage.getItem('token') as string),
      'User-Agent': navigator.userAgent
    },
    onChange(info) {
      console.log('info', info);
      if (info.file.status !== 'uploading') {
        console.log('成功上传', info);
        data?.setFileImg(info?.file?.response?.data?.objectUrl);
      }
      if (info.file.status === 'done') {
        message.success('上传成功');
      } else if (info.file.status === 'error') {
        message.error(`上传失败`);
      }
    }
  };

  useEffect(() => {
    handleWebmsg();
  }, []);
  return (
    <>
      <div className="footer-top">
        <Popover
          content={
            <div style={{ maxWidth: '400px' }}>
              {look?.map((i: any, v: number) => {
                return (
                  <span
                    onClick={() => hide(i)}
                    style={{ padding: '10px', fontSize: '20px', cursor: 'pointer' }}
                    key={v}
                  >
                    {i}
                  </span>
                );
              })}
            </div>
          }
          trigger="click"
          open={open}
          onOpenChange={handleOpenChange}
        >
          <img
            style={{ padding: '5px 0 0 5px', width: '40px', cursor: 'pointer' }}
            src="http://frphn1.chickfrp.com:43961/image/xiao.svg"
            alt=""
          />
        </Popover>
        <div>
          <Upload
            itemRender={(originNode, file, fileList: object[], actions) => (
              <div className="footer-imglist">
                {originNode}
                <div style={{ textAlign: 'right' }}>
                  <Button
                    onClick={() => cancelImg(actions?.remove, file)}
                    style={{ margin: '10px 10px 0 0' }}
                  >
                    取消
                  </Button>
                  <Button type="primary" onClick={() => sendImg(actions?.remove, file)}>
                    发送
                  </Button>
                </div>
              </div>
            )}
            {...propsUpload}
          >
            <img
              style={{ padding: '5px 0 0 5px', width: '40px', cursor: 'pointer' }}
              src="http://frphn1.chickfrp.com:43961/image/upload.svg"
              alt=""
            />
          </Upload>
        </div>
      </div>
      <TextArea
        style={{ fontSize: '20px' }}
        onChange={onChange}
        value={data?.value}
        bordered={false}
        autoSize={{ minRows: 3, maxRows: 5 }}
        rows={3}
        className="textarea-input"
      />
      <div className="btn">
        <Button type="primary" onClick={() => data?.sendserver('text')}>
          发送(s)
        </Button>
      </div>
    </>
  );
}

  

index.less
.info-page {
  background-color: @white;
  height: calc(100% - 56px);
  display: flex;
  flex-wrap: nowrap;
  background-color: #f5f5f5;
  box-sizing: border-box;
  border: 1px solid rgb(218, 213, 213);
}
.info-left {
  overflow: auto;
  min-width: 200px;
  border: 1px solid rgb(218, 213, 213);
}
.info-right {
  display: flex;
  flex-direction: column;
  flex: 1;
  .info-right-header {
    padding: 10px;
    height: auto;
    cursor: pointer;
    border-bottom: 1px solid rgb(218, 213, 213);
  }
  .info-right-main {
    overflow: auto;
    .main-wrap {
      width: 100%;
    }
    flex: 1;
    .main-card {
      display: inline-block;
      margin: 10px;
      // padding: 10px;
    }
  }
  .info-right-footer {
    .textarea-input {
      background-color: #f5f5f5;
    }
    // height: 130px;
    border-top: 1px solid rgb(218, 213, 213);
    .btn {
      padding: 10px;
      text-align: right;
    }
  }
}
.main-card {
  img {
    width: 40px;
    height: 40px;
  }
  .info-img {
    display: flex;
    flex-wrap: nowrap;
  }
  .infoimg {
    padding: 10px;
    border-radius: 5px;
    font-size: 20px;
    word-wrap: break-word;
    border-radius: 5px;
    text-align: left;
    max-width: 300px;
  }
  .info-img-right {
    .infoimg();
    background-color: #95ec69;
  }
  .info-img-left {
    .infoimg();
    background-color: white;
  }
  .main-img-left,
  .main-img-right {
    padding: 10px;
    border-radius: 2px;
    padding: 10px;
    font-size: 20px;
    word-wrap: break-word;
    text-align: left;
    width: 100px;
    height: 100px;
    img {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
  }
  .main-file-left,
  .main-file-right {
    width: 200px;
    display: flex;
    align-items: center;
    span {
      margin-left: auto;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    padding: 10px;
    border-radius: 5px;
    background-color: white;
  }
}

.in-wrap {
  height: calc(100% - 16px);
  background-color: white;
  .edit-top {
    font-size: 18px;
    color: @primary-color;
  }
}
.footer-top {
  display: flex;
  position: relative;
  .footer-imglist {
    padding: 10px;
    border-radius: 5px;
    width: 200px;
    height: auto;
    z-index: 1000;
    background-color: white;
    position: absolute;
    top: -100px;
  }
}

  

聊天时间
const isYear = (timeValue: any) => {
  // 是否为今年
  const dateyear = new Date(timeValue).getFullYear();
  const toyear = new Date().getFullYear();
  // console.log(dateyear, toyear)
  if (dateyear === toyear) {
    return true;
  } else {
    return false;
  }
};
// 转化日期 如2018-7-6 -->(2018-07-06)
const formatNumber = (n: any) => {
  n = n.toString();
  return n[1] ? n : '0' + n;
};
const getWeeken = (date: any) => {
  var weekArray = new Array('周日', '周一', '周二', '周三', '周四', '周五', '周六');
  var week = weekArray[new Date(date).getDay()];
  return week;
};

const formatTime = (date: any) => {
  var t = getTimeArray(date);
  return (
    [t[0], t[1], t[2]].map(formatNumber).join('-') +
    ' ' +
    [t[3], t[4], t[5]].map(formatNumber).join(':')
  );
};

const getTimeArray = (date: any) => {
  date = new Date(date);
  var year = date.getFullYear();
  var month = date.getMonth() + 1;
  var day = date.getDate();
  var hour = date.getHours();
  var minute = date.getMinutes();
  var second = date.getSeconds();
  return [year, month, day, hour, minute, second].map(formatNumber);
};

const isYestday = (timeValue: any) => {
  // 是否为昨天
  const date = new Date(timeValue);
  const today = new Date();
  if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) {
    if (today.getDate() - date.getDate() === 1) {
      return true;
    } else {
      return false;
    }
  } else {
    return false;
  }
};

export const handlerMsgTime = (timeValue: any) => {
  timeValue = new Date(timeValue).getTime();
  var timeNew = new Date().getTime(); // 当前时间
  // console.log('传入的时间', timeValue, timeNew)
  var timeDiffer = timeNew - timeValue; // 与当前时间误差
  // console.log('时间差', timeDiffer)
  var returnTime = '';
  // if (timeDiffer <= 60000) {
  //   // 一分钟内
  //   returnTime = "刚刚";
  // } else if (timeDiffer > 60000 && timeDiffer < 3600000) {
  //   // 1小时内
  //   returnTime = Math.floor(timeDiffer / 60000) + "分钟前";
  // } else if (
  // timeDiffer >= 3600000 &&

  if (timeDiffer < 86400000 && isYestday(timeValue) === false) {
    // 今日
    returnTime = formatTime(timeValue).substr(11, 5);
  } else if (timeDiffer > 3600000 && isYestday(timeValue) === true) {
    // 昨天
    returnTime = '昨天 ' + formatTime(timeValue).substr(11, 5);
  } else if (timeDiffer > 86400000 && timeDiffer <= 518400000) {
    // 星期几
    returnTime = getWeeken(timeValue) + ' ' + formatTime(timeValue).substr(11, 5);
  } else if (
    timeDiffer > 86400000 &&
    isYestday(timeValue) === false &&
    isYear(timeValue) === true
  ) {
    // 今年
    returnTime = formatTime(timeValue).substr(5, 11);
  } else if (
    timeDiffer > 86400000 &&
    isYestday(timeValue) === false &&
    isYear(timeValue) === false
  ) {
    // 不属于今年
    returnTime = formatTime(timeValue).substr(0, 16);
  }
  return returnTime;
};
export const counttime = (v: number) => {
  if (v == 0) {
    return v;
  } else {
    return v - 1;
  }
};
export const checkTime = (a: any, b: any, c: any) => {
  var timeOut = 5 * 60 * 1000; //设置超时时间
  // 上一个
  let syg: any = new Date(a);
  var compareTime = Date.parse(syg);
  let dq: any = new Date(b);
  var currentTime = Date.parse(dq); //对比时间
  if (compareTime - currentTime < timeOut) {
    console.log('您已长时间未操作,即将退出登录 ');
    return null;
  } else {
    return c;
  }
};

  

posted @ 2023-07-17 16:33  zjxgdq  阅读(16)  评论(0编辑  收藏  举报