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;
}
未完待續~