概述
背景
为了实现流程中的提醒功能,实现类似于聊天中的艾特功能,在此做一个VUE+textarea标签实现艾特功能的分享。
基本要求:
-
当删除’@xxx’中其中的任意一个字符或字符串的时候 ‘@xxx’ 这段字符串均被删除
-
‘@xxx’字符串中不得穿插其他内容 即当用户光标在’@xxx’中输入的时候,是无效的
-
艾特的内容成聊天记录形式
实现思路
- 首先绑定textarea v-model=“text”
- 标记@出现的地方 这里我采用’
@ ' 标记选人开始的地方,选人完成后 @xxx 最后一个 x 标记为 ' x
’ ,那么根据开始和`结束则标记了 一个完成的@xxxx 字段,这里我维护为一个数组 - 删除的时候
普通删除 :即删除非@xxx的字符或字符串
删除@xxx当中的字符或字符串
关键是对比 看删除的内容是什么 从哪里开始 删除的长度是多少 - 正是因为每次操作是单一连续的(即每次的操作均在某处插入或删除而非多处,这样就只需要维护两个变量,插入的位置和数量),所以才有可能完成此需求
下面直接上代码
html部分
<template>
<div class="content">
<el-dialog
title="备注"
:visible.sync="dialog"
append-to-body
@close="handcloseDialog()"
width="600px"
class="initDialog"
:close-on-click-modal="false"
>
<div id="app" class="app">
<div class="btncontainer">
<textarea
class="editor"
id="textarea"
ref="textarea"
@keydown="handleKeyDown"
v-model="text"
@input.prevent="handleInput"
@focus="handleFocus"
@mouseup="handleMouseUp"
></textarea>
<div class="board" v-show="showboard">
<div
v-for="(item, index) in list"
@click="handleItemClick(index,item)"
:key="index"
>
{{ item.approvalManName }}
</div>
</div>
</div>
<div class="addBtn" @click="getAddRemarks()">添加</div>
</div>
<div class="remarksList" v-if="dataSource.length>0">
<div class="remarksBox" v-for="(item, i) in dataSource" :key="i">
<div class="remarkContent">{{ item.content }}</div>
<div class="line"></div>
<div class="remarkTips">
<span>{{ item.createUserName }}</span>
<div>{{ item.createTime }}</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
JS部分,记得声明变量,业务代码自动过滤(当做没看见,懒人懒得删)
重点,需要在mounted周期中进行初始化
mounted() {
this.id = this.domainId;
this.firstAuditInfo = this.firstAuditInfoData
this.dialog = this.dialogVisible
window.vm0 = this;
this.watchText();
if(this.domainType&&this.firstAuditInfo) {
this.getAddPeopleData()
this.loadRemoteList()
}
},
函数部分,业务代码请自动过滤
methods: {
getAddRemarks() {
const parameter = {
auditType: this.firstAuditInfo.auditType,
content: this.text,
domainId: this.id || "",
domainType: this.domainType,
entityName: this.firstAuditInfo.entityName,
relationApprovalRemarksList: this.relationApprovalRemarksList,
submitCreateTime: this.firstAuditInfo.submitCreateTime
};
this.loading = true;
httpActionData(this.url.add, parameter, "POST").then((res) => {
if (Number(res.code) === 200) {
this.loading = false;
this.$message(res.msg);
this.loadRemoteList()
this.text= ''
EventBus.$emit("loadRemoteList",this.id,this.domainType,this.firstAuditInfo)
} else {
this.loading = false;
this.$message(res.msg || "数据获取失败!");
}
});
},
getAddPeopleData() {
const parameter = {
auditType: this.firstAuditInfo.auditType,
domainId: this.id || "",
flowId: this.firstAuditInfo.flowId,
submitCreateTime: this.firstAuditInfo.submitCreateTime,
};
this.loading = true;
httpAction(this.url.peopleList, parameter, "GET").then((res) => {
if (Number(res.code) === 200 && res.data) {
this.loading = false;
this.list = res.data || [];
} else {
this.loading = false;
this.$message(res.msg || "数据获取失败!");
}
});
},
// 获取列表数据
loadRemoteList() {
const parameter = {
domainId: this.id || "",
domainType: this.domainType,
auditType: this.firstAuditInfo.auditType,
submitCreateTime: this.firstAuditInfo.submitCreateTime,
};
this.loading = true;
httpAction(this.url.list, parameter, "GET").then((res) => {
if (Number(res.code) === 200 && res.data) {
this.loading = false;
this.dataSource = res.data || [];
} else {
this.loading = false;
this.$message(res.msg || "数据获取失败!");
}
});
},
handcloseDialog() {
this.dialog = false
this.text=''
this.$emit('handcloseDialog',false)
},
watchText() {
this.unwatch = this.$watch("text", function (cv, ov) {
if (ov.length > cv.length) {
let ovlist = [...ov];
let cvlist = [...cv];
let startremove = this.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 = this.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 = [...this.textlist];
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];
}
}
this.textlist = [];
for (let j = 0; j < temptextlist.length; j++) {
if (temptextlist[j] != "ᑒ") {
this.textlist.push(temptextlist[j]);
}
}
if (finalstr !== ov) {
console.log("finalstr", finalstr);
this.text = finalstr;
console.log("之后的this.textlist", this.textlist);
// 重新赋值 textlist
this.unwatch();
setTimeout(() => {
this.watchText();
});
} else {
// 此时校验长度
}
console.log(finalstr, "最终");
this.markdisable = false;
} else {
if (this.markdisable) {
this.text = ov;
this.unwatch();
this.watchText();
return;
}
let startremove = this.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 = this.textlist.slice(0, startremove);
let endlinelist = this.textlist.slice(startremove);
let namelist = [...removestr];
this.textlist = [...beforelinelist, ...namelist, ...endlinelist];
}
});
},
// 当cv大于ov时不一样
findDiffForcvmoreOv(cv, 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;
}
}
}
},
// 查找开始不同的index
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;
},
setCaret() {
var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
console.log(el.childNodes);
range.setStart(el.childNodes[2], 1);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
},
// 监听方向键
handleKeyDown(e) {
if (e.keyCode >= 37 && e.keyCode <= 40) {
// alert(4)
setTimeout(() => {
console.log("位置", e.keyCode, e.target.selectionStart);
let index = e.target.selectionStart - 1;
console.log(index);
let atgroup = this.findAtNameList();
let disabled = false;
for (let j = 0; j < atgroup.length; j++) {
if (
index >= atgroup[j].startindex &&
index < atgroup[j].endindex &&
index != this.text.length - 1
) {
// e.target.selectionStart = atgroup[j].endindex
// e.target.disabled = true
disabled = true;
break;
}
}
this.markdisable = disabled;
}, 5);
}
},
// 处理鼠标左键按下
handleMouseUp(e) {
let index = e.target.selectionStart - 1;
console.log(index);
let atgroup = this.findAtNameList();
let disabled = false;
for (let j = 0; j < atgroup.length; j++) {
if (
index >= atgroup[j].startindex &&
index < atgroup[j].endindex &&
index != this.text.length - 1
) {
// e.target.selectionStart = atgroup[j].endindex
// e.target.disabled = true
disabled = true;
break;
}
}
this.markdisable = disabled;
e.stopPropagation();
e.preventDefault();
},
handleFocus(e) {
if (this.markselectpeople) {
// 表明用户未选择内容
this.textlist.splice(this.operationindex - 1, 0, "@");
console.log("聚焦后的textlist", this.textlist);
this.showboard = false;
this.watchText();
this.markselectpeople = false;
} else {
// 聚焦到非@xxxx的地方
console.log(e.target.selectionStart, e.target.selectionEnd);
}
e.stopPropagation();
e.preventDefault();
},
handleInput(e) {
if (e.data == "@") {
if (this.markdisable) {
return;
}
this.showboard = true;
this.markselectpeople = true;
this.unwatch();
setTimeout(() => {
e.target.blur();
}, 10);
this.operationindex = e.target.selectionStart;
} else {
// this.placeCaretAtEnd(e.target);
// e.target.selectionEnd= 2
// var el = e.target;
// var range = document.createRange();
// console.log(range);
// var sel = window.getSelection();
// console.log(el.childNodes, "元素");
// debugger;
// range.setStart(el, 3);
// range.collapse(true);
// sel.removeAllRanges();
// sel.addRange(range);
}
this.operationindex = e.target.selectionStart;
console.log(this.operationindex);
e.stopPropagation();
e.preventDefault();
},
handleItemClick(index,val) {
let textlist = [...this.text];
let beforeline = textlist.slice(0, this.operationindex);
let endline = textlist.slice(this.operationindex);
console.log(beforeline, endline);
this.text =
beforeline.join("") + this.list[index].approvalManName + endline.join("");
// console.log(JSON.stringify(this.textlist),"操作之前")
this.textlist.splice(this.operationindex - 1, 0, "`@");
// console.log(JSON.stringify(this.textlist),"插入之后")
let beforelinelist = this.textlist.slice(0, this.operationindex);
let endlinelist = this.textlist.slice(this.operationindex);
let namelist = [...this.list[index].approvalManName];
// console.log(beforelinelist,namelist,endlinelist)
namelist[namelist.length - 1] = namelist[namelist.length - 1] + "`";
this.textlist = [...beforelinelist, ...namelist, ...endlinelist];
console.log("点击后的this.textlist:" + this.textlist);
let remarksListData = []
if(this.relationApprovalRemarksList.length>0) {
this.relationApprovalRemarksList.forEach((data)=>{
remarksListData.push(data.nodeId)
})
}
let isAt = remarksListData.indexOf(val.nodeId)
if(isAt == -1 ) {
this.relationApprovalRemarksList.push({
approvalMan: val.approvalMan,
approvalManName: val.approvalManName,
nodeId: val.nodeId,
nodeName: val.nodeName,
})
}
// TODO 添加响应式
setTimeout(() => {
this.watchText();
this.showboard = false;
this.markselectpeople = false;
}, 10);
},
// 找寻@名称列表
findAtNameList() {
let atgroup = [];
let textlist = this.textlist;
let startindex = null;
let endindex = null;
console.log("findAtNameList", [...textlist]);
for (let j = 0; j < textlist.length; j++) {
if (textlist[j] == "`@") {
startindex = j;
// 开始标记
// str += textlist[j]
endindex = null;
}
if (textlist[j][textlist[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;
},
},
css部分
<style scoped lang="scss">
#editable {
width: 100px;
height: 100px;
}
.app{
}
.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: 536px;
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;
}
}
::v-deep .el-dialog__body{
padding: 20px 32px 30px 32px;
}
</style>
总结
在PC端实现@功能,主要的点在于在文本框输入@的时候,通过键盘事件进行操作,然后在@的位置插入对应的值,最后组装数据,通过输入框的事件input拿到数据传给后端。经个人研究,移动端,小程序H5暂时还没有好的方法实现艾特功能,有知道的大佬可以分享一波,谢谢!!!
最后
以上就是拼搏心情为你收集整理的VUE配合textarea实现艾特@功能的全部内容,希望文章能够帮你解决VUE配合textarea实现艾特@功能所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复