2019/8/6
问题描述
简单描述
今天做了一个需求,就是使用textarea进行文本输入时,需要去后台请求字典项来自动完成一些预留的统一本文。刚开始感觉还蛮容易的,但是做到后面坑越来越大,因此记录一下详细的过程。(一个菜鸡后端程序猿,被逼着写前端,巨难受)
需要注意的点
- 由于业务需要,前端的textarea对应的数据是一个数组,即textarea有多项。【如果是只有一个对象的话会很容易】
- 前端监听字段变化有很多种办法,比如使用watch监听数组变化、computer计算属性、监听键盘事件,甚至使用jquery来监听焦点及文本变化等等。
- 需要自动完成提示框能够根据当前文本所在位置来进行显示,类似于QQ的@功能及各种输入法的功能。
- 可以考虑使用键盘上下键来进行选择,按回车键选择自动回复的内容,这样可以很大程度的方便用户。
成品展示
参考文献
如何实现textarea中输入@在当前文本的右下方出现一个div,里面选择人名
问题构思
- 为textarea设置文本变化监听事件,并获取到当前textarea的数组下标。
-
在数据库中新增自动回复的数据,使用like 'xxx%'进行匹配。(业务需求,只考虑开头匹配,中间文本不支持)。 - 根据输入的文本,向后台请求自动完成字段。(由于有多个,因此返回值为一个List数组)
- 将请求到的数组,使用v-for渲染在需要展示的ul组件上。
- 获取当前textarea中光标所在的位置,便于对齐ul组件
- 根据选中的li,将textarea对应数组的内容替换成当前选中li的文本。
扩展选项 - 监听键盘上下键,使得按上下键可以上下选择自动回复的文本。
- 监听键盘回车键,按下回车也可以达到和点击li一样的效果。
【粗体标注的是本文的重点内容,删除线不在本文的介绍范围内】
解决方案及步骤
监听textarea文本变化
由于业务需要,要获取到当前输入的文本值, textarea对应的数组下标以及触发当前文本变化的DOM。 因此,watch和computer方法不太适用(也可能是我没使用好,但他们两都获取不到数组下标以及DOM)。最终我采用了监听键盘事件的方法。尽管功能实现了,但仍旧感觉有所欠缺。。。
使用vue监听textarea键盘事件代码如下:
<textarea style="margin-top: 10px" class="layui-textarea" v-for="(it, dex) in newApprove" :value="it"
v-model="newApprove[dex]" @keyup="autoCompletion(it, dex, $event)"></textarea>
其中newApprove为一个字符串数组:newApprove: [""],
autoCompletion用于监听文本的变化。方法第一个参数为当前文本、第二个为数组下标,第三个为事件。
atuoCompletion方法如下:
autoCompletion: function (text, index, e) {
// 监听上键与下键,拦截默认事件并阻止冒泡
if(e.keyCode === 38 || e.keyCode === 40) {
e.stopPropagation();
e.preventDefault();
return false;
}
var list = $("#list");
// 键盘变化时,隐藏ul
list.hide();
clearTimeout(this.timer); //清除延迟执行
//设置延迟500毫秒执行
this.timer = setTimeout(()=>{
// 保存当前数组下标
vm.newApproveIndex = index;
if(text.length > 0) {
// 请求快捷回复内容, 使用回调来处理成功返回的数据
getAutoCompletionData(text, function (list) {
//后端返回的数据,数据类型为List
vm.autoCompletionData = list;
// 触发显示选项框事件, 并绑定单击事件
showAutoUI(text, e);
});
}
}, 500);
},
渲染获取到的数据
下面的html是当前业务用到的所有html,其中layui是一个前端开源框架,有需要的可以百度下载,不需要的删除即可。
<!-- 这里是一个大的div -->
<div class="layui-field-box box">
<textarea style="margin-top: 10px" class="layui-textarea" v-for="(it, dex) in newApprove" :value="it"
v-model="newApprove[dex]" @keyup="autoCompletion(it, dex, $event)"></textarea>
<!-- 使用ul-li渲染获取到的自动回复内容 -->
<ul id="list" >
<li id="list_li" v-for="(item, index) in autoCompletionData" :tabindex=index
@click="selectedAutoState(item.optName)">{{item.optName}} </li>
</ul>
<!-- 用于点击事件,每点击一次增加一个textarea -->
<div style="text-align: right;">
<i v-if="data.xmlJson.isManyApproval" class="layui-icon layui-icon-add-circle" style="font-size: 30px; color:
#1E9FFF;" @click="addApproveDetail()"></i>
</div>
</div>
获取当前光标位置
获取光标位置需要在监听文本事件,即在showAutoUI(text, e); 方法内部。
以下是showAutoUI方法的代码。第一个参数为变化之后的文本,第二个为当前的点击事件。
function showAutoUI(text, e) {
var curTarget = e.target;
var list = $("#list");
// 获取当前光标所处位置
var position = getPosition(curTarget);
// 光标处于文本最后
if(position === text.length){
// 获取当前DOM的所有style
var iStyle = window.getComputedStyle(curTarget);
//把双字节的替换成两个单字节的然后再获得长度
var len = (text || '').replace(/[^\x00-\xff]/g,"01").length / 2;
var fz = parseFloat(iStyle.fontSize);
var wd = parseFloat(iStyle.width);
var lh = parseFloat(iStyle.lineHeight);
list.css("left", fz * ( len % wd) > wd ? wd : fz * (len % wd) + curTarget.offsetLeft + 5 + "px");
list.css("top", Math.ceil(len / wd) * lh + curTarget.offsetTop + 5 +"px");
list.find("li,.select").each(function({
$(this).removeClass("select")
});
list.show();
}
}
getPosition即获取光标方法,其中参数为使当前文本改动的DOM,即textarea中拥有光标的那一个。getPosition方法代码如下:
//输入框获取光标
function getPosition(element) {
var cursorPos = 0;
if (document.selection) {//IE
var selectRange = document.selection.createRange(); selectRange.moveStart('character', -element.value.length);
cursorPos = selectRange.text.length;
} else if (element.selectionStart || element.selectionStart == '0'{
cursorPos = element.selectionStart;
}
return cursorPos;
}
未完待续~