文本域输入提示,自动补全功能

8 篇文章 0 订阅

<template>
  <div class="auto-container" :id="id">
    <el-input type="textarea" v-model="text" @input="handleChange" @change="changeVal" class="autoInput"></el-input>
    <div class="fix-el" ref="fixEl" :style="styleObj" v-show="show">
      <div class="auto-item" :class="{check:item.check}"  v-for="item in showOptions" 
      :key="item.variableCode" @click="itemClick(item)">{{ item.variableCode }}<span>{{item.variableName }}</span></div>
    </div>

  </div>
</template>
<script>


export default {
  props: {
    value: {
        type:String,
        default:''
      }
  },
  model: {
    prop: 'value',
    event:'change'
  },
  data() {
    return {
      text: '',
      saveText: '',
      lastText: '',
      styleObj: {
        top: '',
        left:''
      },
      str: '',
      options: [],
      show: false,
      showOptions: [],
      isEnd: true,
      cursorPos: 0,
      id:'auto-'+Date.now()
    }
  },
  watch: {
    value(nl, ol) {
      this.text = nl
    }
  },
  created() {
    this.text = this.value
    this.getVariable()
    document.addEventListener('keydown',this.handleKeybord)
  },
  methods: {
    changeVal(data) {
      this.$emit('change', data)
    },
    handleChange(data) {
      let pos = this.getCursorPos(data)
      this.styleObj.left = pos.x +'px'
      this.styleObj.top = pos.y + 'px'
      let str = data
      if (!this.isEnd) {
        str = data.slice(0,this.cursorPos)
      }
      this.getLastStr(str)
    },
    getCursorPos(text) {
      let textarea = document.querySelector(`#${this.id} textarea`)

      // 1. 光标在末尾
      // 2. 光标在中间
      this.cursorPos = textarea.selectionStart
      this.isEnd = this.cursorPos  == this.text.length
      if (!this.isEnd) {
        text = text.slice(0, this.cursorPos)
      }
      // 获取textarea内容宽度
      let contentHeight = textarea.offsetHeight
      let paddingLeft = getComputedStyle(textarea)['paddingLeft'].replace('px', '') / 1
      let paddingRight = getComputedStyle(textarea)['paddingRight'].replace('px', '') / 1
      let paddingTop = getComputedStyle(textarea)['paddingTop'].replace('px', '') / 1
      let lineHeight = getComputedStyle(textarea)['lineHeight'].replace('px', '') / 1
      let font = getComputedStyle(textarea)['font']

      // 下拉框距离文字间距
      let offset = 5

      let contentWidth = textarea.offsetWidth - paddingLeft - paddingRight

      
      var canvas =  document.createElement('canvas')
      var context = canvas.getContext('2d');
      context.font = font;
      let x = paddingLeft, y = lineHeight+paddingTop;
      let total = context.measureText(text).width;
      // 文本不超过一行
      if (total < contentWidth) {
        return {y:lineHeight+offset + paddingTop,x:total+ paddingLeft }
      } else {
       // 多行文本
        var arrText = text.split('');
        // 最后一行文本
        var line = '';
        for (var n = 0; n < arrText.length; n++) {
          var testLine = line + arrText[n];
          var metrics = context.measureText(testLine);
          var testWidth = metrics.width;
          if (testWidth > contentWidth && n > 0) {
            line = arrText[n];
            y += lineHeight;
          } else {
            line = testLine;
          }
        }
        x = context.measureText(line).width
        y = y> contentHeight? contentHeight+ offset :y
        return {x,y}
      }
      
    },
    getLastStr(data) {
      // 获取最后输入的英文单词
      let match = data.match(/[a-zA-Z]+$/)
      if (match) {
        this.str = match[0]
      }
      if (!data || !match) {
        this.showOptions = []
      } else {
        this.showOptions = this.options.filter(item => this.str && item.variableCode&&item.variableCode.includes(this.str))
      }
      this.showOptions.forEach((item,index) => item.check=index==0)
      this.show = this.showOptions.length > 0
      setTimeout(() => {
        let fixEl = document.querySelector(`#${this.id} .fix-el`)
        fixEl.scrollTop = 0
      })
    },
    handleKeybord(e) {
      let arrowMap = ['ArrowDown','ArrowLeft','ArrowUp','ArrowRight']
      // 有下拉框时禁用方向键
      if (arrowMap.includes(e.code) && this.show) {
        let i = this.showOptions.findIndex(item=>item.check)
        if (e.code == 'ArrowDown') {
          this.showOptions.map((item, index) => {
            if (i == this.showOptions.length - 1) {
              item.check = index==i
            } else {
              item.check = index == i+1
            }
          }) 
          this.scroll(true)         
        }
        if (e.code == 'ArrowUp') {
          this.showOptions.map((item, index) => {
            if (i == 0) {
              item.check = index == i
            } else {
              item.check = index == i - 1
            }
          })
          this.scroll(false)
        }
        e.preventDefault()
      }
      // 按下tab 或者回车
      if ((e.code == 'Tab' || e.code=='Enter')&&this.show) {
        this.joinText()
        e.preventDefault()
      }
    },
    scroll(type) {
      let fixEl = document.querySelector(`#${this.id} .fix-el`)
      let checkItem = document.querySelector(`#${this.id} .auto-item.check`)
      if(!checkItem) return
      if (type) {
        if (fixEl.scrollTop + fixEl.offsetHeight < checkItem.offsetHeight + checkItem.offsetTop+20) {
          fixEl.scrollTop = checkItem.offsetHeight + checkItem.offsetTop
        }
      } else {
        if (Math.abs(fixEl.scrollTop - checkItem.offsetTop) < checkItem.offsetHeight) {
          fixEl.scrollTop = checkItem.offsetTop-fixEl.offsetHeight+checkItem.offsetHeight
        }
        
      }
    },
    joinText() {
      let item = this.showOptions.find(item => item.check)
      if (this.isEnd) {
        this.text = this.text.replace(/[a-zA-Z]+$/, '')
        this.text += item.variableCode
      } else {
       let  beforeText = this.text.slice(0, this.cursorPos)
       let  afterText = this.text.slice(this.cursorPos) 
        beforeText = beforeText.replace(/[a-zA-Z]+$/, '') + item.variableCode
        this.text = beforeText + afterText
        setTimeout(() => {
          let textarea = document.querySelector(`#${this.id} textarea`)
          textarea.focus()
          textarea.selectionStart = textarea.selectionEnd= beforeText.length
        })
      }
      this.showOptions = []
      this.show = false
    },
    itemClick(item) {
      item.check = true
      this.joinText()
    },
    async getVariable() {
      let res = await queryVariableList({ formulaType: [1] })
      if (res) {
        this.options = res.map(item => { 
          return {
            variableCode: item.variableCode,
            variableName: item.variableName,
            check:false
          }
        })
      }
    }
 
  }
}

</script>
<style lang="less">
.auto-container {
  position: relative;
  .autoInput {
    width: 100% !important;
  }
  .fix-el {
    position: absolute;
    z-index: 9000;
    width: 250px;
    max-height: 200px;
    background-color: #fff;
    overflow-y: auto;
    box-shadow: 0px 1px 8px rgba(181, 176, 176, 0.3);
    .auto-item {
      padding-left: 5px;
      line-height: 1;
      padding: 10px;
      font-size: 14px;
      &.check {
        background-color: #D7A256;
        color: #fff;
      }
    }
  }

}
</style>

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值