chats

1.图片拖拽

const editableDiv = document.getElementById('editable');
 
editableDiv.addEventListener('dragover', function(event) {
  event.preventDefault(); // 阻止默认行为
});
 
editableDiv.addEventListener('drop', function(event) {
  event.preventDefault(); // 阻止默认行为
  const files = event.dataTransfer.files; // 获取拖放的文件
  
  // 处理文件
  handleFiles(files);
});
 
function handleFiles(files) {
  [...files].forEach(file => {
    // 检查文件类型
    if (!file.type.startsWith('image/')) {
      alert('请拖入图片文件');
      return;
    }
 
    const reader = new FileReader();
    reader.onload = function(event) {
      // 创建图片元素并插入到div
      const img = document.createElement('img');
      img.src = event.target.result;
      editableDiv.appendChild(img);
    };
    reader.readAsDataURL(file); // 读取文件
  });
}

2.文件复制

document.getElementById('editable-div').addEventListener('paste', function(event) {
  // 阻止默认粘贴行为
  event.preventDefault();
 
  // 获取剪贴板数据
  const clipboardData = event.clipboardData || window.clipboardData;
  const items = clipboardData.items;
 
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
 
    // 检查是否是文件
    if (item.kind === 'file') {
      const file = item.getAsFile();
 
      // 根据文件类型处理
      if (file.type === 'text/plain') {
        // 对于.txt文件,直接插入文本
        const reader = new FileReader();
        reader.onload = function(e) {
          const text = e.target.result;
          document.getElementById('editable-div').textContent += text;
        };
        reader.readAsText(file);
      } else if (file.type === 'application/pdf' || file.type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') {
        // 对于PDF和PPTX文件,可以尝试将它们转换为图片或者提供预览链接
        const fileURL = URL.createObjectURL(file);
        // 这里可以插入一个iframe或者图片来预览文件
        console.log('Preview URL:', fileURL);
      } else {
        console.log('Unsupported file type:', file.type);
      }
    }
  }
});

3.根据视频图片地址获取其宽高

//视频:
getVideoMsg(file) {
  return new Promise((resolve) => {
    let videoElement = document.createElement("video")
    videoElement.src = URL.createObjectURL(file)
    videoElement.addEventListener("loadedmetadata", function () {
      resolve({
        duration: videoElement.duration,
        height: videoElement.videoHeight,
        width: videoElement.videoWidth,
      })
    })
  })
},
//图片:
getPictureInfo(url){
    const image = new Image();
    image.src = url;
    image.onload = function(){
        const height = image.height;
        const width = image.width;
    }
}

4.将div封装成聊天发送框

import { Button, Flex, Input, Popover, message } from "antd";
import { FC, useState, useRef, useEffect } from "react";
import emojiIcon from "@/asisite/image/boshiweb_biaoqing.png";
import imgIcon from "@/asisite/image/tupian.png";
import FileIcon from "@/asisite/image/tupian copy.png";
import SendIcon from "@/asisite/image/fasong.svg";
import { Short } from "../Allert/short";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "@/store/store";
import {
  setFocusToEnd,
  insertAtCursor,
  handleSendMessage,
  SendMsgs,
} from "./operate";
import { ChangeRecords } from "@/store/reducer/UserInfoSlice";
import { ChangeCount } from "@/store/reducer/ListSlice";
import UploadPicture from "../UploadPicture";
import ShuCaiIcon from "@/asisite/image/tupian copy 3.png";
import shortIcon from "@/asisite/image/tupian copy 2.png";
import Emoji from "./Emoji";
const TextArea = Input.TextArea;
interface FooterProps {
  daRef: any;
}

export const Footer: FC<FooterProps> = ({ daRef }) => {
  const [valutOpen, setValutOpen] = useState<boolean>(false);
  const dispatch = useDispatch();
  const records = useSelector((state: RootState) => state.UserInfoSlice.record);
  const chatInfo = useSelector(
    (state: RootState) => state.UserInfoSlice.chatInfo
  );
  const userInfo = useSelector(
    (state: RootState) => state.UserInfoSlice.userInfo
  );
  const ChangeValutOpen = (val: boolean) => {
    setValutOpen(val);
  };
  const connectState = useSelector(
    (state: RootState) => state.ListSlice.connectState
  );
  // 图片区分
  const [pics, setPics] = useState<any>({});

  const sendImg = (data: any) => {
    if (connectState && chatInfo?.userID.length) {
      SendMsgs(102, chatInfo?.userID, data, changemsg);
    } else {
      message.error("disconnect...");
    }
  };

  const HandleKeyDown = (e: any) => {
    if (e.key === "Enter" && !e.ctrlKey) {
      e.preventDefault();
      handleSendMessage(refs, chatInfo, pics, userInfo, dispatch, setPics,setFlag);
    } else if (e.key === "Enter" && e.ctrlKey) {
      const parser = new DOMParser();
      const doc = parser.parseFromString("\n", "text/html");
      const nodes = Array.from(doc.body.childNodes);
      insertAtCursor(nodes);
    }
  };
  const refs = useRef<any>(null);
  // 监听粘贴事件=>粘贴时添加图片标识
  const [pic, setPic] = useState<any>({});
  useEffect(() => {
    if (JSON.stringify(pic) !== "{}") {
      setPics((pics: any) => {
        if (JSON.stringify(pics) !== "{}") {
          return { ...pics, ...pic };
        } else {
          return pic;
        }
      });
    }
  }, [pic]);
  useEffect(() => {
    const handlePaste = (e: any) => {
      const clp = e.clipboardData;
      e.preventDefault();

      if (clp?.items[0].type.includes("text/html")) {
        let text = clp.getData("text/html") || "";
        const imgMeta = "<meta charset='utf-8'>";
        if (text.includes(imgMeta)) {
          text = text.replaceAll(imgMeta, "");
          const parser = new DOMParser();
          const doc = parser.parseFromString(text, "text/html");
          const nodes = Array.from(doc.body.childNodes);
          insertAtCursor(nodes);
        } else {
          text = text.replace(/<\/?[^>]+(>|$)/g, "");
          insertAtCursor([document.createTextNode(text)]);
        }
      }
      // 粘贴文件=》待开发

      if (clp?.items[0].type.includes("text/plain")) {
        const text = clp.getData("text/plain") || "";

        const reader = new FileReader();
        const blob = clp?.items[0].getAsFile();
        console.log("file==", blob);
        reader.onload = () => {
          const parser = new DOMParser();
          const doc = parser.parseFromString(
            reader.result as string,
            "text/html"
          );
          const nodes = Array.from(doc.body.childNodes);
          insertAtCursor(nodes);
        };
        reader.readAsDataURL(blob!);
      }

      const images = [] as HTMLImageElement[];
      const imageItems = [...clp.items].filter((item: any) =>
        item.type.includes("image")
      );
      // const obj: any = {};
      imageItems.map((item: DataTransferItem) => {
        const blob = item.getAsFile();
        // 文件流
        console.log("blob===", blob);
        const reader = new FileReader();
        reader.onload = () => {
          const obj: any = {};
          const image = new Image();
          image.src = reader.result as string;
          const str = new Date().getTime().toString();
          image.title = str;
          obj[str] = blob;
          setPic(obj);
          images.push(image);
          if (images.length === imageItems.length) {
            insertAtCursor([...images]);
          }
        };

        reader.readAsDataURL(blob!);
      });
    };

    const editor = refs?.current;
    editor?.addEventListener("paste", handlePaste);

    return () => {
      editor?.removeEventListener("paste", handlePaste);
    };
  }, []);

  // emoji框
  const [emojiOpen, setEmojiOpen] = useState<boolean>(false);
  const handleOpenChange = (newOpen: boolean) => {
    setEmojiOpen(newOpen);
  };
  useEffect(() => {
    if (refs && refs.current) {
      setFocusToEnd();
    }
  }, [chatInfo]);

  // 消息单发
  const changemsg = (val: any) => {
    const arr: any = [...records];
    arr.push(val);
    dispatch(ChangeRecords(arr));
    dispatch(ChangeCount(false));
  };

  useEffect(() => {
    console.log("pics===", pics);
  }, [pics]);

  // 当发送内容为空时
  const [flag, setFlag] = useState<boolean>(false);
  useEffect(()=>{
    if(flag){
      setTimeout(()=>{
        setFlag(false)
      },1000)
    }
  },[flag])
  return (
    <>
          <Flex className="box-head" align="center">
            <Popover
              content={<Emoji Hide={setEmojiOpen} />}
              trigger="click"
              overlayClassName="emoji"
              open={emojiOpen}
              onOpenChange={handleOpenChange}
            >
              <img className="mr4 point" src={emojiIcon} alt="" />
            </Popover>
            {/* <img className="mr4 point" src={imgIcon} alt="" /> */}
            <UploadPicture SendMsg={sendImg} />
            <img className="mr4 point" src={FileIcon} alt="" />
            <img className="mr4 point" src={ShuCaiIcon} alt="" />
            <Short
              Open={valutOpen}
              ChangeOpen={ChangeValutOpen}
              daRef={daRef}
            />
          </Flex>
          <Flex align="center">
            <div
              className="input"
              contentEditable="true"
              ref={refs}
              id="editable-div"
              defaultValue="please input"
              onKeyDown={HandleKeyDown}
            />
            <Popover content={<div className="send-tootile">不能发送空白信息</div>} open={flag}>
              <Flex
                className="btn point"
                onClick={() =>
                  handleSendMessage(
                    refs,
                    chatInfo,
                    pics,
                    userInfo,
                    dispatch,
                    setPics,
                    setFlag
                  )
                }
              >
                <div className="mr5">Send</div>
                <img src={SendIcon} alt="" width={10} />
              </Flex>
            </Popover>
          </Flex>
    </>
  );
};

1.聊天发送框聚焦
export const setFocusToEnd = () => {
  var editableDiv = document.getElementById("editable-div");
  if (editableDiv) {
    editableDiv.focus(); // 聚焦元素
    if (
      typeof window.getSelection != "undefined" &&
      typeof document.createRange != "undefined"
    ) {
      var range = document.createRange();
      range.selectNodeContents(editableDiv);
      range.collapse(false);
      var sel: any = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);
    } else {
      if ("createTextRange" in document.body) {
        // 这里是针对IE的代码
        const rage = (document.body as any).createTextRange();
        // ... 使用range进行相关操作
        if (rage != "undefined") {
          rage.moveToElementText(editableDiv);
          rage.collapse(false);
          rage.select();
        }
      }
    }
    editableDiv.scrollTop = editableDiv.scrollHeight;
  }
};

2.项发送框插入节点
export const insertAtCursor = (nodes: Node[], rangeIdx = 0) => {
  const selection = window.getSelection();
  const range = selection!.getRangeAt(rangeIdx);
  range.deleteContents();
  nodes.map((node) => {
    range.insertNode(node);
    range.setStartAfter(node);
  });
  range.setEndAfter(nodes[nodes.length - 1]);
  selection!.removeAllRanges();
  selection!.addRange(range);

  // Move the inserted nodes to the input element if they are not already there
  const inputElement = document.getElementById("editable-div");
  nodes.forEach((node) => {
    let parent = node.parentElement;
    while (parent) {
      if (parent === inputElement) return;
      parent = parent.parentElement;
    }
    inputElement?.appendChild(node);
  });

  return range && setFocusToEnd();
};

3.混合消息发送处理函数
export const handleSendMessage = (
  refs: any,
  chatInfo: any,
  pics: any,
  userInfo: any,
  dispatch: any,
  setPics: any,
  setFlag: any
) => {
  if (refs && refs.current) {
    let msg: any[] = [];
    let idx: number[] = [];
    for (let i = 0; i < refs.current.childNodes.length; i++) {
      let node = refs.current.childNodes[i];
      console.log("node===", node.nodeType);
      if (node.nodeType === Node.TEXT_NODE) {
        if (node.textContent.length) {
          msg[i] = { type: 101, msg: node.textContent };
        }
      } else if (node.nodeName === "IMG") {
        msg[i] = {
          type: 102,
          msg: node.getAttribute("title"),
          src: node.getAttribute("src"),
        };
        idx.push(i);
      }
    }
    console.log("msg=====", msg);
    idx.push(refs.current.childNodes.length);
    const mesg: any[] = [];
    let i = 0;
    idx.forEach((item: any, idx: number) => {
      let msgs: string = "";
      msg.slice(i, item).forEach((itm: any) => {
        msgs += itm.msg;
      });
      if (msgs.length) {
        mesg.push({ type: 101, msg: msgs });
      }
      if (msg[item]?.msg?.length) {
        mesg.push(msg[item]);
      }
      i = item + 1;
    });
    console.log("mesg=====", mesg);
    if (mesg.length) {
      if (chatInfo?.userID) {
        const img: any[] = mesg?.filter((item: any) => item.type === 102);
        const text: any[] = mesg?.filter((item: any) => item.type === 101);
        text?.forEach((item: any) => {
          dispatch(
            SendMsg({
              type: item?.type,
              recvID: chatInfo?.userID,
              body: { msg: item?.msg },
            })
          );
          dispatch(ChangeCount(false));
        });
        for (let i = 0; i < img.length; i++) {
          const id: any = new Date().getTime();
          const image = new Image();
          image.src = img[i]?.src;
          image.onload = function () {
            const height = image.height;
            const width = image.width;
            const msg: any = {
              id,
              sendID: userInfo?.im_user_id,
              recvID: chatInfo?.userID,
              status: 1,
              contentType: 102,
              pictureElem: {
                sourcePicture: { url: img[i]?.src, width, height },
              },
              sendTime: id,
            };
            dispatch(PushRecords(msg));
          };

          api.user.UploadImage(id, { image: pics[img[i]?.msg] }).then((res) => {
            console.log(res);
            const image = new Image();
            image.src = res?.data?.data?.url;
            image.onload = function () {
              const height = image.height;
              const width = image.width;

              dispatch(ChangeCount(false));
              dispatch(
                SendMsg({
                  type: 102,
                  recvID: chatInfo?.userID,
                  body: {
                    type: 102,
                    picBaseInfo: {
                      type: pics[img[i]?.msg]?.type,
                      size: pics[img[i]?.msg]?.size,
                      url: res?.data?.data?.url,
                      width,
                      height,
                    },
                    url: res?.data?.data?.url,
                  },
                  conversationID: chatInfo?.conversationID || null,
                })
              );
            };
          });
        }
        setTimeout(() => {
          refs.current.innerHTML = "";
          setPics({});
        }, 200);
      }
    } else {
      setFlag(true);
    }
  }
};

5.react-redux reduxjs/toolkit 实现同步异步方法 (读取initialState中变量的值JSON.parse(JSON.stringify(state)))

import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit";
import { getSDK, CbEvents } from "open-im-sdk-wasm";
import store from "../store";
import { ChangeCount } from "./ListSlice";
const IMSDK = getSDK();

type initialStateType = {
  userInfo: any;
  // app 通道id
  apps: any[];
  channels_id: string;
  staff_avatar: string;
  connectState: number | null;
  local: string;
  // 未读消息徽标
  badge: any;
  // group
  selected: string;
  // chatRight
  orderSel: string;
  // record
  recordSel: string;
  // 放刷新聊条记录消失
  record: any[];
  // 当前聊天对象基础信息
  chatInfo: any;
  // 登录状态
  loginState: boolean;
  // 滚动获取聊天记录参数
  startClientMsgID: string;
  lastMinSeq: number;
  // 页面加载状态
  loading: boolean;
  // chat loading
  chatLoading: boolean;
};
const initialState: initialStateType = {
  userInfo: {},
  channels_id: "all",
  staff_avatar: "",
  connectState: 1,
  local: "en-us",
  badge: {},
  selected: "group1",
  orderSel: "order",
  recordSel: "record",
  record: [],
  chatInfo: {},
  loginState: false,
  startClientMsgID: "",
  lastMinSeq: 0,
  apps: [],
  loading: true,
  chatLoading: false,
};

export const UserInfoLice = createSlice({
  name: "UserInfoLice",
  initialState: initialState,
  reducers: {
    setUserInfo: (state, action: PayloadAction<any>) => {
      state.userInfo = action.payload;
    },
    ClearDate: (state, action: PayloadAction<boolean>) => {
      if (action.payload) {
        state.userInfo = {};
        state.channels_id = "all";
        state.staff_avatar = "";
        state.connectState = 1;
        state.local = "en-us";
        state.badge = {};
        state.selected = "group1";
        state.orderSel = "order";
        state.recordSel = "record";
        state.record = [];
        state.chatInfo = {};
        state.loginState = false;
        state.startClientMsgID = "";
        state.lastMinSeq = 0;
        state.apps = [];
        state.loading = true;
        state.chatLoading = false;
      }
    },
    changeChannelsId: (state, action: PayloadAction<string>) => {
      state.channels_id = action.payload;
    },
    setStaffAvatar: (state, action: PayloadAction<string>) => {
      state.staff_avatar = action.payload;
    },

    setConnectStates: (state, action: PayloadAction<number | null>) => {
      state.connectState = action.payload;
    },
    changeLocal: (state, action: PayloadAction<string>) => {
      state.local = action.payload;
    },
    ChangeBadge: (state, action: PayloadAction<any>) => {
      state.badge = action.payload;
    },
    ChangeSelected: (state, action: PayloadAction<string>) => {
      state.selected = action.payload;
    },
    ChangeOrder: (state, action: PayloadAction<string>) => {
      state.orderSel = action.payload;
    },
    ChangeRecord: (state, action: PayloadAction<string>) => {
      state.recordSel = action.payload;
    },
    ChangeRecords: (state, action: PayloadAction<any[]>) => {
      state.record = action.payload;
    },
    PushRecords:(state,action: PayloadAction<any>) => {
      if(action.payload){
        const arr = [...state.record];
        arr.push(action.payload);
        state.record = [...arr];
      }
      
    },
    ChangeChatInfo: (state, action: PayloadAction<any>) => {
      state.chatInfo = action.payload;
    },
    ShuaData: (state, action: PayloadAction<boolean>) => {
      if (action.payload) {
        state.record = [];
        state.chatInfo = {};
        state.apps = [];
      }
    },
    ChangeConnectLoginState: (state, action: PayloadAction<boolean>) => {
      state.loginState = action.payload;
    },
    ChangeStartClientMsgID: (state, action: PayloadAction<string>) => {
      state.startClientMsgID = action.payload;
    },
    ChangeLastMinSeq: (state, action: PayloadAction<number>) => {
      state.lastMinSeq = action.payload;
    },
    ChangeApps: (state, action: PayloadAction<any[]>) => {
      state.apps = action.payload;
    },
    ChangeLoading: (state, action: PayloadAction<boolean>) => {
      state.loading = action.payload;
    },
    ChangeChatLoading: (state, action: PayloadAction<boolean>) => {
      state.chatLoading = action.payload;
    },
  },
  extraReducers:(builder)=>{
    builder.addCase(SendMsg.pending,(state)=>{});
    builder.addCase(SendMsg.fulfilled,(state,action:PayloadAction<any>)=>{
      const msg:any = action?.payload?.msg;
      if (msg?.contentType === 102) {
        const list:any[] = [...state.record];
        const obj: any = { ...msg };
        const id = Number(
          obj?.pictureElem?.sourcePicture?.url
            ?.split("/")
            [
              obj?.pictureElem?.sourcePicture?.url?.split("/").length - 1
            ]?.split(".")[0]
        );
        const arr: any = [...list];
        let idx: number | null = null;
        arr?.forEach((item: any, index: number) => {
          if (Number(item?.id) === id) {
            console.log("msg=====", obj, arr, item);
            idx = index;
            // item = {...obj}
            // item.status = obj?.status;
            // item.pictureElem = obj?.pictureElem;
            // item.sendTime = obj?.sendTime;
          }
        });
        if (idx) {
          arr[idx] = { ...obj };
          console.log("arrs===", arr);
          state.record = arr;
          // dispatch(ChangeRecords(arr));
          // dispatch(ChangeCount(false));
          // store.dispatch(ChangeCount(false));
        }
      } else {
        const list:any[] = state.record;
        const arr: any = [...list];
        console.log("==arr", arr);
        arr.push(msg);
        state.record = arr;
        // dispatch(ChangeRecords(arr));
        // dispatch(ChangeCount(false));
        // store.dispatch(ChangeCount(false))
      }
    });
    builder.addCase(SendMsg.rejected,(state,action)=>{
      console.log(action.error.message);
    })
  }
});

// 导出actions
export const {
  setUserInfo,
  changeChannelsId,
  ClearDate,
  setStaffAvatar,
  setConnectStates,
  changeLocal,
  ChangeBadge,
  ChangeSelected,
  ChangeOrder,
  ChangeRecord,
  ChangeRecords,
  ChangeChatInfo,
  ShuaData,
  ChangeConnectLoginState,
  ChangeLastMinSeq,
  ChangeStartClientMsgID,
  ChangeApps,
  ChangeLoading,
  ChangeChatLoading,
  PushRecords,
} = UserInfoLice.actions;

// 创建异步方法修改聊天记录
export const SendMsg = createAsyncThunk("sendMsg", async (dat: any) => {
  if (dat && Number(dat?.type) === 101) {
    const { data: message } = await IMSDK.createTextMessage(dat?.body?.msg);
    const res = await IMSDK.sendMessage({
      recvID: dat?.recvID,
      groupID: "",
      message,
    });
    console.log('fsdafd00===',res)
    return {msg:res?.data};
  }else if(dat&&Number(dat?.type)===102){
    if(dat?.conversationID){
      const { data: messageList } = await IMSDK.getAdvancedHistoryMessageList({
        lastMinSeq:0,
        count:20,
        startClientMsgID:"",
        conversationID:dat.conversationID
      })
      const { data: message } = await IMSDK.createImageMessageByURL({
        sourcePicture: dat?.body?.picBaseInfo,
        bigPicture: dat?.body?.picBaseInfo,
        snapshotPicture: dat?.body?.picBaseInfo,
        sourcePath: dat?.body?.url,
      });
      const res = await IMSDK.sendMessageNotOss({
        recvID:dat?.recvID,
        groupID: "",
        message,
      })
      console.log('fsdafd00===',messageList)
      return {msg:res?.data,record:[...messageList.messageList],newMsg:dat?.newMsg};
    }
    
  }
});

// 导出reducer,在创建store中使用
export default UserInfoLice.reducer;

6.emoji普通表情和国旗表情转换成图片 .

export const convertToUnicode = (str: string) => {
  return str?.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function (match: any) {
    return `<img src=${
      process.env.REACT_APP_EMOJI_API +
      match.codePointAt(0).toString(16) +
      ".png"
    } alt='' width=${16} height=${16}/>`;
  });
};

// 识别超链接
export const translateHtml = (msg: string) => {
  let re = /(http[s]?:\/\/([\w-]+.)+([:\d+])?(\/[\w-\.\/\?%&=]*)?)/gi;
  let s = msg?.replace(re, function (a) {
    return ' <a href="' + a + '" target=_blank class="recordA">' + a + "</a>";
  });
  return s;
};

// 识别emoji国旗
export const findEmojiFlags = (str: string) => {
  return str?.replace(
    /[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g,
    function (match: any) {
      let str2: string = match?.replace(
        /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
        function (match2: any) {
          return match2.codePointAt(0).toString(16) + "-";
        }
      );
      str2 = str2
        .split("-")
        ?.filter((item: any) => item?.length)
        .join("-");
      return `<img src=${
        process.env.REACT_APP_EMOJI_API + str2 + ".png"
      } alt='' width=${16} height=${16}/>`;
    }
  );
};

//举例
<div dangerouslySetInnerHTML={{__html: convertToUnicode(findEmojiFlags(translateHtml(item?.textElem?.content?.toString())?.toString()))}}></div>

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值