能@xx删除,
代码
<template>
<div>
<div class="btncontainer">
<textarea
class="editor"
ref="textarea"
@keydown="handleKeyDown"
v-model="text"
@input.prevent="handleInput"
@focus="handleFocus"
@mouseup="handleMouseUp"
></textarea>
<template v-if="showboard">
<div
class="board"
:style="{
position: 'absolute',
left: position.x + 'px',
top: position.y + 'px'
}"
>
<div
v-for="(item, index) in list"
@click="handleItemClick(index, item)"
:key="index"
>
{{ item.approvalManName }}
</div>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from "vue";
import getCaretCoordinates from "@/utils/getCaretCoordinates"; //获取光标的位置,具体代码在下面
const text = ref();
const list = ref([
{
approvalManName: "张三",
id: "202"
},
{
approvalManName: "李四",
id: "203"
}]);
const showboard = ref(false);
const textlist = ref<any[]>([]);
const markdisable = ref(false);
const relationApprovalRemarksList = ref([]);
const markselectpeople = ref(false);
const operationindex = ref(-1);
let unwatch = () => {};
const position = ref({ x: 0, y: 0 });
function watchText() {
unwatch = watch(
() => text.value,
(cv, ov) => {
console.log(cv, ov);
if (ov) {
if (ov.length > cv.length) {
let ovlist = [...ov];
let cvlist = [...cv];
let startremove = findDiffStart(cv, ov);
let removestr = "";
let difflength = ovlist.length - cvlist.length;
for (let j = startremove; j <= startremove + difflength - 1; j++) {
removestr += ovlist[j];
}
//console.log("对比结果", startremove, ov, cv, removestr);
//console.log(removestr, "匹配器结果");
let atnamelist = findAtNameList();
//console.log("atnamelist", atnamelist, removestr, startremove);
for (let j = 0; j < atnamelist.length; j++) {
for (
let k = atnamelist[j].startindex;
k <= atnamelist[j].endindex;
k++
) {
if (k >= startremove && k <= startremove + removestr.length - 1) {
atnamelist[j].remove = true;
}
}
}
let temp = [...ov];
let tempstr = [...ov];
let finalstr = "";
let temptextlist = [...textlist.value];
//console.log("temp", temp);
for (let j = 0; j < temp.length; j++) {
// 拿出@xxx并标记
for (let k = 0; k < atnamelist.length; k++) {
if (
atnamelist[k].remove &&
j >= atnamelist[k].startindex &&
j <= atnamelist[k].endindex
) {
// 使用ᑒ特殊符号进行标记
tempstr[j] = "ᑒ";
temptextlist[j] = "ᑒ";
}
}
// 拿出正常删除的并标记
if (j >= startremove && j <= startremove + removestr.length - 1) {
tempstr[j] = "ᑒ";
temptextlist[j] = "ᑒ";
}
}
for (let j = 0; j < tempstr.length; j++) {
if (tempstr[j] != "ᑒ") {
finalstr += tempstr[j];
}
}
textlist.value = [];
for (let j = 0; j < temptextlist.length; j++) {
if (temptextlist[j] != "ᑒ") {
textlist.value.push(temptextlist[j]);
}
}
if (finalstr !== ov) {
//console.log("finalstr", finalstr);
text.value = finalstr;
//console.log("之后的textlist", textlist.value);
// 重新赋值 textlist
unwatch();
setTimeout(() => {
watchText();
});
} else {
// 此时校验长度
}
//console.log(finalstr, "最终");
markdisable.value = false;
} else {
if (markdisable.value) {
text.value = ov;
unwatch();
watchText();
return;
}
let startremove = findDiffForcvmoreOv(cv, ov);
let removestr = "";
let difflength = cv.length - ov.length;
for (let j = startremove; j <= startremove + difflength - 1; j++) {
removestr += cv[j];
}
//console.log("对比结果" + removestr);
let beforelinelist = textlist.value.slice(0, startremove);
let endlinelist = textlist.value.slice(startremove);
let namelist = [...removestr];
textlist.value = [...beforelinelist, ...namelist, ...endlinelist];
}
} else {
if (markdisable.value) {
text.value = ov;
unwatch();
watchText();
return;
}
let startremove = findDiffForcvmoreOv(cv, ov);
let removestr = "";
let difflength = 0;
if (ov) {
difflength = cv.length - ov.length;
} else {
difflength = cv.length - 0;
}
for (let j = startremove; j <= startremove + difflength - 1; j++) {
removestr += cv[j];
}
//console.log("对比结果" + removestr);
let beforelinelist = textlist.value.slice(0, startremove);
let endlinelist = textlist.value.slice(startremove);
let namelist = [...removestr];
textlist.value = [...beforelinelist, ...namelist, ...endlinelist];
}
}
);
}
// 查找开始不同的index
function findDiffStart(cv, ov) {
let str1 = ov;
let str2 = cv;
let str1list = [...str1];
let str2list = [...str2];
let thestartindex = null;
for (let j = 0; j < str1list.length; j++) {
let sliced = str1list.slice(j, j + str1.length - str2.length);
let find = false;
for (let k = 0; k < str2list.length; k++) {
let beforestr = str2list.slice(0, j);
let centerstr = sliced;
let endstr = str2list.slice(j);
//console.log([...beforestr, ...centerstr, ...endstr].join(""), "最终结果");
if ([...beforestr, ...centerstr, ...endstr].join("") == str1) {
find = true;
break;
}
}
if (find) {
thestartindex = j;
//console.log(j, "哈哈哈");
break;
}
}
return thestartindex;
}
// 当cv大于ov时不一样
function findDiffForcvmoreOv(cv, ov) {
if (ov) {
let shorter = ov;
let longer = cv;
let longerlist = [...longer];
let shorterlist = [...shorter];
let thestartindex = null;
for (let j = 0; j < shorterlist.length + 1; j++) {
let insertindex = j;
for (let k = 0; k < longerlist.length; k++) {
let sliced = longerlist.slice(k, k + longer.length - shorter.length);
let begin = shorterlist.slice(0, j);
let center = sliced;
let end = shorterlist.slice(j);
let finalstr = [...begin, ...center, ...end].join("");
if (finalstr == longer) {
return j;
}
}
}
}
}
// 监听方向键
function handleKeyDown(e) {
if (e.keyCode >= 37 && e.keyCode <= 40) {
setTimeout(() => {
//console.log("位置", e.keyCode, e.target.selectionStart);
let index = e.target.selectionStart - 1;
//console.log(index);
let atgroup = findAtNameList();
let disabled = false;
for (let j = 0; j < atgroup.length; j++) {
if (
index >= atgroup[j].startindex &&
index < atgroup[j].endindex &&
index != text.value.length - 1
) {
// e.target.selectionStart = atgroup[j].endindex
// e.target.disabled = true
disabled = true;
break;
}
}
markdisable.value = disabled;
}, 5);
}
}
// 处理鼠标左键按下
function handleMouseUp(e) {
let index = e.target.selectionStart - 1;
//console.log(index);
let atgroup = findAtNameList();
let disabled = false;
for (let j = 0; j < atgroup.length; j++) {
if (
index >= atgroup[j].startindex &&
index < atgroup[j].endindex &&
index != text.value.length - 1
) {
// e.target.selectionStart = atgroup[j].endindex
// e.target.disabled = true
disabled = true;
break;
}
}
markdisable.value = disabled;
e.stopPropagation();
e.preventDefault();
}
function handleFocus(e) {
if (markselectpeople.value) {
// 表明用户未选择内容
textlist.value.splice(operationindex.value - 1, 0, "@");
//console.log("聚焦后的textlist", textlist.value);
showboard.value = false;
watchText();
markselectpeople.value = false;
} else {
// 聚焦到非@xxxx的地方
//console.log(e.target.selectionStart, e.target.selectionEnd);
}
e.stopPropagation();
e.preventDefault();
}
function handleInput(e) {
if (e.data == "@") {
if (markdisable.value) {
return;
}
showboard.value = true;
markselectpeople.value = true;
unwatch();
setTimeout(() => {
e.target.blur();
}, 10);
operationindex.value = e.target.selectionStart;
} else {
}
operationindex.value = e.target.selectionStart;
// 获取父级元素, 光标位置
let coordinates = getCaretCoordinates(e.target, e.target.selectionEnd);
//console.log(coordinates);
position.value = {
x: coordinates.left + 10,
y: coordinates.top
};
console.log(position.value, 666);
e.stopPropagation();
e.preventDefault();
}
function handleItemClick(index, val) {
let textlists = [...text.value];
let beforeline = textlists.slice(0, operationindex.value);
let endline = textlists.slice(operationindex.value);
//console.log(beforeline, endline);
text.value =
beforeline.join("") + list.value[index].approvalManName + endline.join("");
textlist.value.splice(operationindex.value - 1, 0, "`@");
let beforelinelist = textlist.value.slice(0, operationindex.value);
let endlinelist = textlist.value.slice(operationindex.value);
let namelist = [...list.value[index].approvalManName];
namelist[namelist.length - 1] = namelist[namelist.length - 1] + "`";
textlist.value = [...beforelinelist, ...namelist, ...endlinelist];
let remarksListData = [];
if (relationApprovalRemarksList.value.length > 0) {
relationApprovalRemarksList.value.forEach(data => {
remarksListData.push(data.id);
});
}
//存储选中的@选中的数组
let isAt = remarksListData.indexOf(val.id);
if (isAt == -1) {
relationApprovalRemarksList.value.push({
approvalManName: val.approvalManName,
id: val.id
});
}
console.log(relationApprovalRemarksList.value, 888);
// TODO 添加响应式
setTimeout(() => {
watchText();
showboard.value = false;
markselectpeople.value = false;
}, 10);
}
// 找寻@名称列表
function findAtNameList() {
let atgroup = [];
let textlists = textlist.value;
let startindex = null;
let endindex = null;
console.log("findAtNameList", [...textlists]);
for (let j = 0; j < textlists.length; j++) {
if (textlists[j] == "`@") {
startindex = j;
// 开始标记
// str += textlist[j]
endindex = null;
}
if (textlists[j][textlists[j].length - 1] == "`") {
// 结束符号
if (startindex !== null) {
endindex = j;
}
}
if (startindex !== null && endindex !== null) {
let item = {
startindex: startindex,
endindex: endindex
};
startindex = null;
endindex = null;
atgroup.push(item);
}
}
return atgroup;
}
onMounted(() => {
watchText();
});
</script>
<style scoped="scoped" lang="scss">
.remarksList {
display: flex;
flex-direction: column;
margin-top: 30px;
height: 400px;
overflow-y: auto;
.remarksBox {
display: flex;
flex-direction: column;
background: #f7f9fc;
border-radius: 3px;
padding: 15px;
margin-bottom: 20px;
.remarkContent {
color: #313840;
font-size: 14px;
line-height: 22px;
span {
color: #1f5fb1;
}
}
.line {
width: 506px;
height: 1px;
border: 1px solid #e5eaef;
margin: 15px 0;
}
.remarkTips {
display: flex;
align-items: center;
span {
color: #838a92;
font-size: 14px;
line-height: 20px;
margin-right: 10px;
}
div {
color: #aeb6c0;
font-size: 14px;
line-height: 20px;
}
}
}
}
.btncontainer {
display: flex;
position: relative;
flex-direction: column;
textarea:disabled {
background-color: white;
}
.totest,
.tohome {
font-size: 12px;
height: 30px;
width: 80px;
border: 1px solid gray;
border-radius: 10px;
margin-top: 10px;
text-align: center;
line-height: 30px;
margin-left: 10px;
}
.text {
width: 200px;
height: 200px;
}
.board {
width: 200px;
max-height: 181px;
overflow: scroll;
cursor: pointer;
margin-top: 5px;
background: #ffffff;
box-shadow: 0px 3px 8px 0px rgba(166, 166, 166, 0.15);
div {
height: 32px;
line-height: 32px;
cursor: pointer;
font-size: 16px;
padding: 0 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
div:hover {
background: #1f5fb1;
color: #fff;
}
}
}
.addBtn {
width: 60px;
height: 36px;
background: #1f5fb1;
border-radius: 4px;
font-size: 14px;
color: #fff;
line-height: 36px;
text-align: center;
margin-top: 12px;
cursor: pointer;
}
.editor {
margin: 0 auto;
width: 100%;
height: 102px;
background: #fff;
border: 1px solid #e5eaef;
border-radius: 4px;
text-align: left;
padding: 8px 12px;
overflow: auto;
line-height: 20px;
&:focus {
outline: none;
}
}
</style>
2.getCaretCoordinates.js
// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
var properties = [
"direction", // RTL support
"boxSizing",
"width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
"height",
"overflowX",
"overflowY", // copy the scrollbar for IE
"borderTopWidth",
"borderRightWidth",
"borderBottomWidth",
"borderLeftWidth",
"borderStyle",
"paddingTop",
"paddingRight",
"paddingBottom",
"paddingLeft",
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
"fontStyle",
"fontVariant",
"fontWeight",
"fontStretch",
"fontSize",
"fontSizeAdjust",
"lineHeight",
"fontFamily",
"textAlign",
"textTransform",
"textIndent",
"textDecoration", // might not make a difference, but better be safe
"letterSpacing",
"wordSpacing",
"tabSize",
"MozTabSize"
];
var isBrowser = typeof window !== "undefined";
var isFirefox = isBrowser && window.mozInnerScreenX != null;
export default function getCaretCoordinates(element, position, options) {
if (!isBrowser) {
throw new Error(
"textarea-caret-position#getCaretCoordinates should only be called in a browser"
);
}
var debug = (options && options.debug) || false;
if (debug) {
var el = document.querySelector(
"#input-textarea-caret-position-mirror-div"
);
if (el) {
el.parentNode.removeChild(el);
}
}
// mirrored div
var div = document.createElement("div");
div.id = "input-textarea-caret-position-mirror-div";
document.body.appendChild(div);
var style = div.style;
var computed = window.getComputedStyle
? getComputedStyle(element)
: element.currentStyle; // currentStyle for IE < 9
// default textarea styles
style.whiteSpace = "pre-wrap";
if (element.nodeName !== "INPUT") style.wordWrap = "break-word"; // only for textarea-s
// position off-screen
style.position = "absolute"; // required to return coordinates properly
if (!debug) style.visibility = "hidden"; // not 'display: none' because we want rendering
// transfer the element's properties to the div
properties.forEach(function (prop) {
style[prop] = computed[prop];
});
if (isFirefox) {
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if (element.scrollHeight > parseInt(computed.height))
style.overflowY = "scroll";
} else {
style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
}
div.textContent = element.value.substring(0, position);
// the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
if (element.nodeName === "INPUT")
div.textContent = div.textContent.replace(/\s/g, "\u00a0");
var span = document.createElement("span");
// Wrapping must be replicated *exactly*, including when a long word gets
// onto the next line, with whitespace at the end of the line before (#7).
// The *only* reliable way to do that is to copy the *entire* rest of the
// textarea's content into the <span> created at the caret position.
// for inputs, just '.' would be enough, but why bother?
span.textContent = element.value.substring(position) || "."; // || because a completely empty faux span doesn't render at all
div.appendChild(span);
var coordinates = {
top: span.offsetTop + parseInt(computed["borderTopWidth"]),
left: span.offsetLeft + parseInt(computed["borderLeftWidth"])
};
if (debug) {
span.style.backgroundColor = "#aaa";
} else {
document.body.removeChild(div);
}
return coordinates;
}