如何通過Vue實現@人的功能
阿新 • • 發佈:2021-12-27
本文采用,同時增加滑鼠點選事件和一些頁面小優化
基本結構
新建一個sandBox.vue檔案編寫功能的基本結構
<div class="content"> <!--文字框--> <div class="editor" ref="divRef" contenteditable @keyup="handkeKeyUp" @keydown="handleKeyDown" ></div> <!--選項--> <AtDialog v-if="showDialog" :visible="showDialog" :position="position" :queryString="queryString" @onPickUser="handlePickUser" @onHide="handleHide" @onShow="handleShow" ></AtDialog> </div> <script> import AtDialog from '../components/AtDialog' export default { name: 'sandBox',components: { AtDialog },data () { return { node: '',// 獲取到節點 user: '',// 選中項的內容 endIndex: '',// 游標最後停留位置 queryString: '',// 搜尋值 showDialog: false,// 是否顯示彈窗 position: { x: 0,y: 0 }// 彈窗顯示位置 } },methods: { // 獲取游標位置 getCursorIndex () { const selection = window.getSelection() return selection.focusOffset // 選擇開始處 focusNode 的偏移量 },// 獲取節點 getRangeNode () { const selection = window.getSelection() return selection.focusNode // 選擇的結束節點 },// 彈窗出現的位置 getRangeRect () { const selection = window.getSelection() const range = selection.getRangeAt(0) // 是用於管理選擇範圍的通用物件 const rect = range.getClientRects()[0] // 擇一些文字並將獲得所選文字的範圍 const LINE_HEIGHT = 30 return { x: rect.x,y: rect.y + LINE_HEIGHT } },// 是否展示 @ showAt () { const node = this.getRangeNode() if (!node || node.nodeType !== Node.TEXT_NODE) return false const content = node.textContent || '' const regx = /@([^@\s]*)$/ const match = regx.exec(content.slice(0,this.getCursorIndex())) return match && match.length === 2 },// 獲取 @ 使用者 getAtUser () { const content = this.getRangeNode().textContent || '' const regx = /@([^@\s]*)$/ const match = regx.exec(content.slice(0,this.getCursorIndex())) if (match && match.length === 2) { return match[1] } return undefined },// 建立標籤 createAtButton (user) { const btn = document.createElement('span') btn.style.display = 'inline-block' btn.dataset.user = ON.stringify(user) btn.className = 'at-button' btn.contentuFDaWjZmgEditable = 'false' btn.textContent = `@${user.name}` const wrapper = document.createElement('span') wrapper.style.display = 'inline-block' wrapper.contentEditable = 'false' const spaceElem = document.createElement('span') spaceElem.style.whiteSpace = 'pre' spaceElem.textContent = '\u200b' spaceElem.contentEditable = 'false' const clonedSpaceElem = spaceElem.cloneNode(true) wrapper.appendChild(spaceElem) wrapper.appendChild(btn) wrapper.appendChild(clonedSpaceElem) return wrapper },replaceString (raw,replacer) { return raw.replace(/@([^@\s]*)$/,replacer) },// 插入@標籤 replaceAtUser (user) { const node = this.node if (node && user) { const content = node.textContent || '' const endIndex = this.endIndex const preSlice = this.replaceString(content.slice(0,endIndex),'') const restSlice = content.slice(endIndex) const parentNode = node.parentNode const nextNode = node.nextSibling const previousTextNode = new Text(preSlice) const nextTextNode = new Text('\u200b' + restSlice) // 新增 0 寬字元 const atButton = this.createAtButton(user) parentNode.removeChild(node) // 插在文字框中 if (nextNode) { parentNode.insertBefore(previousTextNode,nextNode) parentNode.insertBefore(atButton,nextNode) parentNode.insertBefore(nextTextNode,nextNode) } else { parentNode.appendChild(previousTextNode) parentNode.appendChild(atButton) parentNode.appendChild(nextTextNode) } // 重置游標的位置 const range = new Range() const selection = window.getSelection() range.setStart(nextTextNode,0) range.setEnd(nextTextNode,0) selection.removeAllRanges() selection.addRange(range) } },// 鍵盤抬起事件 handkeKeyUp () { if (this.showAt()) { const node = this.getRangeNode()const endIndex = this.getCursorIndex() this.node = node this.endIndex = endIndex this.position = this.getRangeRect() this.queryString = this.getAtUser() || '' this.showDialog = true } else { this.showDialog = false } },// 鍵盤按下事件 handleKeyDown (e) { if (this.showDialog) { if (e.code === 'ArrowUp' || e.code === 'ArrowDown' || e.code === 'Enter') { e.preventDefault() } } },// 插入標籤後隱藏選擇框 handlePickUser (user) { this.replaceAtUser(user) this.user = user this.showDialog = false },// 隱藏選擇框 handleHide () { this.showDialog = false },// 顯示選擇框 handleShow () { this.showDialog = true } } } </script> <style scoped lang="s"> .content { font-family: sans-serif; h1{ text-align: center; } } .editor { margin: 0 auto; width: 600px; height: 150px; background: #fff; border: 1px solid blue; border-radius: 5px; text-align: left; padding: 10px; overflow: auto; line-height: 30px; &:focus { outline: none; } } </style>
如果添加了點選事件,節點和游標位置獲取,需要在【鍵盤抬起事件】中獲取,並儲存到data
// 鍵盤抬起事件 handkeKeyUp () { if (this.showAt()) { const node = this.getRangeNode() // 獲取節點 const endIndex = this.getCursorIndex() // 獲取游標位置 this.node = node this.endIndex = endIndex this.position = this.getRangeRect() this.queryString = this.getAtUser() || '' this.showDialog = true } else { this.showDialog = false } },
新建一個元件,編輯彈窗選項
<template> <div class="wrapper" :style="{position:'fixed',top:position.y +'px',left:position.x+'px'}"> <div v-if="!mockList.length" class="empty">無搜尋結果</div> <div v-for="(item,i) in mockList" :key="item.id" class="item" :class="{'active': i === index}" ref="usersRef" @click="clickAt($event,item)" @mouseenter="hoverAt(i)" > <div class="name">{{item.name}}</div> </div> </div> </template> <script> const mockData = [ { name: 'HTML',id: 'HTML' },{ name: 'CSS',id: 'CSS' },{ name: '',id: 'Java' },id: 'JavaScript' } ] export default { name: 'AtDialog',props: { visible: Boolean,position: Object,queryString: String },data () { return { users: [],index: -1,mockList: mockData } },watch客棧: { queryString (val) { val ? this.mockList = mockData.filter(({ name }) => name.startsWith(val)) : this.mockList = mockData.slice(0) } },mounted () { document.addEventListener('keyup',this.keyDownHandler) },destroyed () { document.removeEventListener('keyup',methods: { keyDownHandler (e) { if (e.code === 'Escape') { this.$emit('onHide') return } // 鍵盤按下 => ↓ if (e.code === 'ArrowDown') { if (this.index >= this.mockList.length - 1) { this.index = 0 } else { this.index = this.index + 1 } } // 鍵盤按下 => ↑ if (e.code === 'ArrowUp') { if (this.index <= 0) { this.index = this.mockList.length - 1 } else { this.index = this.index - 1 } } // 鍵盤按下 => 回車 if (e.code === 'Enter') { if (this.mockList.length) { const user = { name: this.mockList[this.index].name,id: this.mockList[this.index].id } this.$emit('onPickUser',user) this.index = -1 } } },clickAt (e,item) { const user = { name: item.name,id: item.id } this.$emit('onPickUser',user) this.index = -1 },hoverAt (index) { this.index = index } } } </script> <style scoped lang="scss"> .wrapper { width: 238px; border: 1px solid #e4e7ed; border-radius: 4px; background-color: #fff; box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); box-sizing: border-box; padding: 6px 0; } .empty{ font-size: 14px; padding: 0 20px; color: #999; } .item { font-size: 14px; padding: 0 20px; line-height: 34px; cursor: pointer; color: #606266; &.active { background: #f5f7fa; color: blue; .id { color: blue; } } &:first-child { border-radius: 5px 5px 0 0; } &:last-child { border-radius: 0 0 5px 5px; } .id { font-size: 12px; color: rgb(83,81,81); } } </style>
以上就是如何通過Vue實現@人的功能的詳細內容,更多關於Vue @人功能的資料請關注我們其它相關文章!