最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何通過Vue實(shí)現(xiàn)@人的功能

 更新時(shí)間:2021年12月25日 16:06:38   作者:HAN_Trevor  
這篇文章主要介紹了如何通過vue實(shí)現(xiàn)微博中常見的@人的功能,同時(shí)增加鼠標(biāo)點(diǎn)擊事件和一些頁面小優(yōu)化。感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

本文采用vue,同時(shí)增加鼠標(biāo)點(diǎn)擊事件和一些頁面小優(yōu)化

基本結(jié)構(gòu)

新建一個(gè)sandBox.vue文件編寫功能的基本結(jié)構(gòu)

 <div class="content">
    <!--文本框-->
    <div
      class="editor"
      ref="divRef"
      contenteditable
      @keyup="handkeKeyUp"
      @keydown="handleKeyDown"
    ></div>
    <!--選項(xiàng)-->
    <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: '', // 獲取到節(jié)點(diǎn)
      user: '', // 選中項(xiàng)的內(nèi)容
      endIndex: '', // 光標(biāo)最后停留位置
      queryString: '', // 搜索值
      showDialog: false, // 是否顯示彈窗
      position: {
        x: 0,
        y: 0
      }// 彈窗顯示位置
    }
  },
  methods: {
    // 獲取光標(biāo)位置
    getCursorIndex () {
      const selection = window.getSelection()
      return selection.focusOffset // 選擇開始處 focusNode 的偏移量
    },
    // 獲取節(jié)點(diǎn)
    getRangeNode () {
      const selection = window.getSelection()
      return selection.focusNode // 選擇的結(jié)束節(jié)點(diǎn)
    },
    // 彈窗出現(xiàn)的位置
    getRangeRect () {
      const selection = window.getSelection()
      const range = selection.getRangeAt(0) // 是用于管理選擇范圍的通用對(duì)象
      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
    },
    // 創(chuàng)建標(biāo)簽
    createAtButton (user) {
      const btn = document.createElement('span')
      btn.style.display = 'inline-block'
      btn.dataset.user = JSON.stringify(user)
      btn.className = 'at-button'
      btn.contentEditable = '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)
    },
    // 插入@標(biāo)簽
    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)
        }
        // 重置光標(biāo)的位置
        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()
        }
      }
    },
    // 插入標(biāo)簽后隱藏選擇框
    handlePickUser (user) {
      this.replaceAtUser(user)
      this.user = user
      this.showDialog = false
    },
    // 隱藏選擇框
    handleHide () {
      this.showDialog = false
    },
    // 顯示選擇框
    handleShow () {
      this.showDialog = true
    }
  }
}
</script>
 
<style scoped lang="scss">
  .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>

如果添加了點(diǎn)擊事件,節(jié)點(diǎn)和光標(biāo)位置獲取,需要在【鍵盤抬起事件】中獲取,并保存到data

 // 鍵盤抬起事件
    handkeKeyUp () {
      if (this.showAt()) {
        const node = this.getRangeNode() // 獲取節(jié)點(diǎn)
        const endIndex = this.getCursorIndex() // 獲取光標(biāo)位置
        this.node = node 
        this.endIndex = endIndex 
        this.position = this.getRangeRect()
        this.queryString = this.getAtUser() || ''
        this.showDialog = true
      } else {
        this.showDialog = false
      }
    },

新建一個(gè)組件,編輯彈窗選項(xiàng)?

<template>
<div
  class="wrapper"
  :style="{position:'fixed',top:position.y +'px',left:position.x+'px'}">
  <div v-if="!mockList.length" class="empty">無搜索結(jié)果</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: 'Java', id: 'Java' },
  { name: 'JavaScript', 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', this.keyDownHandler)
  },
  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實(shí)現(xiàn)@人的功能的詳細(xì)內(nèi)容,更多關(guān)于Vue @人功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue組件之Tooltip的示例代碼

    Vue組件之Tooltip的示例代碼

    這篇文章主要介紹了Vue組件之Tooltip的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • vue3中單文件組件<script?setup>實(shí)例詳解

    vue3中單文件組件<script?setup>實(shí)例詳解

    <script?setup>是vue3中新引入的語法糖,目的是簡(jiǎn)化使用Composition?API時(shí)冗長(zhǎng)的模板代碼,下面這篇文章主要給大家介紹了關(guān)于vue3中單文件組件<script?setup>的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • Vue如何實(shí)現(xiàn)多頁面配置以及打包方式

    Vue如何實(shí)現(xiàn)多頁面配置以及打包方式

    這篇文章主要介紹了Vue如何實(shí)現(xiàn)多頁面配置以及打包方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • vue3實(shí)戰(zhàn)教程之a(chǎn)xios的封裝和環(huán)境變量

    vue3實(shí)戰(zhàn)教程之a(chǎn)xios的封裝和環(huán)境變量

    這篇文章主要給大家介紹了關(guān)于vue3實(shí)戰(zhàn)教程之a(chǎn)xios的封裝和環(huán)境變量的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-02-02
  • 簡(jiǎn)單了解Vue + ElementUI后臺(tái)管理模板

    簡(jiǎn)單了解Vue + ElementUI后臺(tái)管理模板

    這篇文章主要介紹了簡(jiǎn)單了解Vue + ElementUI后臺(tái)管理模板,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Vue實(shí)例中el和data的兩種寫法小結(jié)

    Vue實(shí)例中el和data的兩種寫法小結(jié)

    這篇文章主要介紹了Vue實(shí)例中el和data的兩種寫法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-11-11
  • 如何解決vue-json-editor無法輸入中文、重影問題

    如何解決vue-json-editor無法輸入中文、重影問題

    文章介紹了如何解決vue-json-editor組件無法輸入中文和重影的問題,通過修改源碼并使用vue-json-edit-fix-cn組件來 fix 這兩個(gè)問題,同時(shí),文章還提供了如何移除舊的依賴包并安裝新的依賴包的步驟
    2025-01-01
  • Vue的虛擬DOM和diff算法你了解嗎

    Vue的虛擬DOM和diff算法你了解嗎

    這篇文章主要為大家詳細(xì)介紹了Vue的虛擬DOM和diff算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • vue 組件 全局注冊(cè)和局部注冊(cè)的實(shí)現(xiàn)

    vue 組件 全局注冊(cè)和局部注冊(cè)的實(shí)現(xiàn)

    下面小編就為大家分享一篇vue 組件 全局注冊(cè)和局部注冊(cè)的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • vue如何實(shí)現(xiàn)級(jí)聯(lián)選擇器功能

    vue如何實(shí)現(xiàn)級(jí)聯(lián)選擇器功能

    這篇文章主要介紹了vue如何實(shí)現(xiàn)級(jí)聯(lián)選擇器功能問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04

最新評(píng)論

吉林市| 仁怀市| 玉溪市| 瑞安市| 陇南市| 临沧市| 鄯善县| 扬州市| 曲松县| 阿克陶县| 垦利县| 元江| 新沂市| 盘山县| 林甸县| 翁源县| 文昌市| 龙口市| 康平县| 钟祥市| 晋江市| 墨江| 赤城县| 白朗县| 北安市| 兰西县| 科尔| 庆云县| 南丹县| 马公市| 平和县| 邢台县| 桐柏县| 息烽县| 云浮市| 清流县| 湖北省| 霍城县| 治县。| 儋州市| 建瓯市|