使用Vue實現文本自動完成(快捷回覆)

2019/8/6


問題描述


簡單描述

今天做了一個需求,就是使用textarea進行文本輸入時,需要去後臺請求字典項來自動完成一些預留的統一本文。剛開始感覺還蠻容易的,但是做到後面坑越來越大,因此記錄一下詳細的過程。(一個菜雞後端程序猿,被逼着寫前端,巨難受)

需要注意的點
  1. 由於業務需要,前端的textarea對應的數據是一個數組,即textarea有多項。【如果是隻有一個對象的話會很容易】
  2. 前端監聽字段變化有很多種辦法,比如使用watch監聽數組變化、computer計算屬性、監聽鍵盤事件,甚至使用jquery來監聽焦點及文本變化等等。
  3. 需要自動完成提示框能夠根據當前文本所在位置來進行顯示,類似於QQ的@功能及各種輸入法的功能。
  4. 可以考慮使用鍵盤上下鍵來進行選擇,按回車鍵選擇自動回覆的內容,這樣可以很大程度的方便用戶。
成品展示

在這裏插入圖片描述

參考文獻

如何實現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;
}

未完待續~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章