1. 程式人生 > 其它 >vue基於textarea 實現@人的功能

vue基於textarea 實現@人的功能

此需求來源於一個pc端的網頁,要求使用者打出@的時候 能夠出現候選人列表,當點選其中一個之後,文字則顯示@xxxx

應產品要求

   1,當刪除'@xxx'中其中的任意一個字元或字串的時候 '@xxx' 這段字串均被刪除

   2,'@xxx'字串中不得穿插其他內容 即當用戶游標在'@xxx'中輸入的時候,是無效的

思路:1,  首先繫結textarea v-model="text"

           2,  標記@出現的地方 這裡我採用' `@ ' 標記選人開始的地方,選人完成後 @xxx 最後一個 x 標記為 ' x` '  ,那麼根據 ` 開始 和` 結束 則標記了 一個完成的@xxxx 欄位,這裡我維護為一個數組

           3,刪除的時候,

                      1i, 普通刪除 :即刪除非@xxx的字元或字串

                      2i,刪除@xxx當中的字元或字串

                      3i ,關鍵是對比 看刪除的內容是什麼 從哪裡開始 刪除的長度是多少

           4,正是因為每次操作是單一連續的(即每次的操作均在某處插入或刪除而非多處,這樣就只需要維護兩個變數,插入的位置和數量),所以才有可能完成此需求

預備知識: 1,textarea獲取當前游標的位置 dom.selectionStart

                   2,  vue動態監聽操作,有時候我們認為某一次的操作不應該觸發watch,這時會採用動態監聽如下, $watch會返回一個watcher函式 當我們呼叫他的時候 會取消掉vue對text的監聽

                    

registerWatch() {
   this.watcher = this.$watch('text',function(cv,ov) {
          //xxxxx
     })

}     

     //操作text的方法
     handleTextOptions() {
         // 我們認為本次不需要監聽text
        this.watcher()
        this.text = xxx
    }
     // 預先

           3, 維護一個

下面貼程式碼 

           

<template>
  <div id="app">
    <div class="btncontainer">
      <textarea
        id
="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)" :key="index" > {{ item.name }} </div> </div> </div> <router-view /> </div> </template> <script> export default { name: "App", data() { return { env: process.env.NODE_ENV, active: 0, text: "", showboard: false, list: [], tempinput: "", markselectpeople: false, // 標記開始選人 markdisable: false, unwatch: () => {}, operationindex: null, oldtext: "", textlist: [], }; }, mounted() { window.vm0 = this; this.watchText(); for (let j = 0; j < 20; j++) { let item = { name: "王尼瑪" + j, id: j, }; this.list.push(item); } }, methods: { 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) { 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].name + 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].name]; // console.log(beforelinelist,namelist,endlinelist) namelist[namelist.length - 1] = namelist[namelist.length - 1] + "`"; this.textlist = [...beforelinelist, ...namelist, ...endlinelist]; console.log("點選後的this.textlist:" + this.textlist); // 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; }, goToHome() { if (this.$route.path == "/") { return; } this.$router.push({ name: "HelloWorld", }); }, goToTest() { if (this.$route.path == "/Test") { return; } this.$router.push({ name: "Test", }); }, }, }; </script> <style lang="less"> #app { #editable { width: 100px; height: 100px; } .btncontainer { display: flex; textarea:disabled { background-color: white; } textarea { width: 200px; height: 200px; } .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: 100px; max-height: 100px; overflow: scroll; cursor: pointer; .item { border-bottom: 1px solid gray; height: 20px; line-height: 20px; cursor: pointer; } } } } </style>

最終效果如下: