1. 程式人生 > 程式設計 >如何通過Vue實現@人的功能

如何通過Vue實現@人的功能

本文采用,同時增加滑鼠點選事件和一些頁面小優化

如何通過Vue實現@人的功能

如何通過Vue實現@人的功能

基本結構

新建一個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.contentuFDaWjZmg
Editable = '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 @人功能的資料請關注我們其它相關文章!