针对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>