针对im输入框的一种处理方式

<template>
  <div class="chatInput">
    <!-- 通过contenteditable使普通的div变成富文本模式
 		由于要实现富文本的本地实时搜索,富文本没有change事件,而只能借用compositionstart,compositionend,input三个事件组合模拟出change
		compositionstart:当用户使用拼音输入法开始输入拼音时触发,在键入之前只会触发一次(因此此方法可以判断用户是否在使用拼音输入法)
 		compositionend:中文输入完成时触发
		input:输入什么都会触发
		设置一个变量doing,初始值为false,compositionstart触发将doing值为true,拼音输入完成触发compositionend,此时将doing置为false,开始搜索,input只有在doing为false时触发,即表示此时未使用拼音,直接搜索-->
    <div
      id="edit"
      ref="edit"
      contenteditable
      @compositionstart="doing = true"
      @compositionend="handleSearch"
      @input="handleKey"
      @keyup="carReply"
      @paste.prevent.trim="paste"
    >
      {{ splitContent.trim() }}
    </div>
    <div class="btn_wrap flex-end">
      按Ctrl+Enter换行,按Enter输出
      <el-button @click="send" type="primary"> 发送</el-button>
    </div>
  </div>
</template>

<script setup lang="ts" name="chatInput">
import { ref, onMounted } from "vue";
import { httpRequest } from "../chatUtil";
import { fileUpload } from "@/api/modules/chat";

interface PropsInterface {
  send: any;
}
const props = defineProps<PropsInterface>();
const doing = ref(false);

const edit = ref() as any;

// 这两属性都是控制输入框输入拆分的
let splitContent = "";
// 鼠标光标值
let lastEditRange = null as any;

onMounted(() => {
  // 初始化输入框聚焦,设置初始的选定对象节点
  const editDom = document.querySelector("#edit") as any;
  // 在失去焦点前保留上一次的选定对象节点
  editDom.addEventListener("blur", (e: any) => setSection());
  editDom.focus();
  setSection();
});
const setSection = () => {
  // 获取选定对象
  let selection = getSelection() as any;
  if (selection.getRangeAt && selection.rangeCount) {
    // 设置最后光标对象
    lastEditRange = selection.getRangeAt(0);
  }
};

const handleSearch = () => {
  doing.value = false;
  // 此时搜索
};
const handleKey = () => {
  const e = window.event as any;
  if (!doing.value) {
    // 此时搜索
  }
};
// 按键控制输出
const carReply = () => {
  const e = window.event as any;
  //   ctrl+enter实现换行,实测只有通过execCommened的方式插入换行符才可以实现换行
  if (e.ctrlKey && e.keyCode == 13) {
    document.execCommand("insertText", false, "\n");
  } else if (e.keyCode == 13) {
    send();
  }
};

// 文本框粘贴
const paste = (e: any) => {
  // 获取粘贴板数据
  const clipdata = e.clipboardData || (window as any).clipboardData;
  const files = clipdata.files;
  const text = clipdata.getData("text/plain");

  // 将文本按照html的格式粘贴进输入框中
  text && document.execCommand("insertHTML", false, text);
  if (files.length) {
    Array.from(files).forEach((file: any) => {
      // 只针对图片进行粘贴
      if (file.type.match(/^image\//)) {
        const windowURL = window.URL || window.webkitURL;
        // 将file生成一个blob链接,可用于本地直接打开,也可以用于下载
        const url = windowURL.createObjectURL(file);
        document.execCommand("insertImage", true, url);
      }
    });
  }
};

const send = () => {
  const edit = document.querySelector("#edit") as any;

  if (edit.childNodes.length == 0) {
    // 内容为空,给个提示阻止了
    return;
  }

  const _arr = Array.from(edit.childNodes) as any;

  // 递归遍历文本框内容,取dom元素的最深一级(实测发现富文本的html结构很乱,取到最深一层才能拿到内容)
  contentRevese(_arr);
  splitContent && props.send(splitContent);
  // 输出后清空
  splitContent = "";
  edit.textContent = "";
};

const contentRevese: any = async (_arr: any) => {
  for (let i = 0; i < _arr.length; i++) {
    // 拿最深层
    if (_arr[i].childNodes.length > 0) {
      contentRevese(Array.from(_arr[i].childNodes));
    } else {
      if (_arr[i].tagName == "IMG") {
        // 在取文本内容时,多行的文本应同时取,不能像图片一样,取到了就直接发送
        props.send(splitContent);
        splitContent = "";
        // blob路径转file
        let img = (await httpRequest(_arr[i].src)) as any;

        // 该blob对象用完后,应及时回收资源,节省内存
        const windowURL = window.URL || window.webkitURL;
        windowURL.revokeObjectURL(_arr[i].src);
        const formData = new FormData() as any;
        formData.append("files", img);
        formData.append("type", "3");
        const { data } = (await fileUpload(formData)) as any;
        props.send(`img[${data.url}][${data.file_name}][size:${data.size}]`);
      } else {
        // 匹配下一段文本加换行
        splitContent += _arr[i].textContent + "\n";
      }
    }
  }
};
</script>
03-23 10:52