my-edit-tags/src/EditTags.vue

<template>
  <div class="my-edit-tags">
    <div class="my-edit-tags__warp clearfix">
      <el-tag
        :size="size"
        v-bind="$attrs"
        :key="`${tag}_${index}`"
        v-for="(tag, index) in dynamicTags"
        :closable="!readOnly"
        :disable-transitions="true"
        @close="handleClose(tag)">
        {{tag}}
      </el-tag>

      <el-input
        :style="inputStyle"
        :class="inputClass"
        v-model="inputValue"
        ref="saveTagInput"
        size="mini" 
        v-if="inputVisible"
        @keyup.enter.native="handleInputConfirm"
        @blur="handleInputConfirm"
      >
      </el-input> 
      <el-tag v-else  key="edit" v-bind="$attrs" class="button-new-tag" :style="addTagStyle" :size="size"  @click="showInput" v-show="!readOnly">+ 添加</el-tag>
      
      
    </div>
  </div>
</template>

<script>
/**
 * edit-tags 批量标签编辑工具
 * @module $ui/components/my-edit-tags
 */
export default {
  name: 'MyEditTags',
  /**
   * 参数属性
   * @member props
   * @property {Boolean} [isRight = true] 输入表单在后面
   * @property {Array} [tagsList = []] 双向绑定 标签数组
   * @property {Boolean} [readOnly = false] 是否只读
   * @property {Arrays} [devides = [',', ',', '|']] 字符串分割符号集合
   * @property {String} [inputWidth = '80px'] 输入框长度
   * @property {String} [size = ''] 整体大小(‘’,small, medium, mini)
   * @property {Function} [validate = function(keys, vm) {return Promise}] 验证输入值的函数,参数:当前编辑的并经过分割后的标签数组, 当前组件, 输出: Promise
   * @property {Function} [invalidateHandle = function(keys, vm) {return Promise}] 验证失败的回调函数,参数:当前编辑的并经过分割后的标签数组, 当前组件, 输出: Promise
   */
  props: {
    isRight: {
      type: Boolean,
      default: true
    },
    tagsList: {
      type: Array,
      default: () => { return [] }
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    devides: {
      type: Array,
      default: () => { return [',', ',', '|'] }
    },
    inputWidth: {
      type: String,
      default: '80px'
    },
    size: {
      type: String,
      default: '',
      validator: function (value) {
        return ['', 'medium', 'small', 'mini'].indexOf(value) !== -1
      }
    },
    validate: {
      type: Function
    },
    invalidateHandle: {
      type: Function
    }
  },
  data() {
    return {
      dynamicTags: [],
      inputVisible: false,
      inputValue: ''
    }
  },
  computed: {
    inputClass() {
      const className = ['input-new-tag']
      const type = this.size ? `is-${this.size}` : 'is-large'
      className.push(type)
      return className
    },
    inputStyle() {
      const margin = this.isRight ? {marginLeft: '5px'} : {marginRight: '5px'}
      return {
        width: this.inputWidth, 
        float: this.isRight ? 'none' : 'left',
        ...margin
      }
    },
    addTagStyle() {
      const margin = this.isRight ? {marginLeft: '5px', marginRight: '0px'} : {marginLeft: '0px', marginRight: '5px'}
      return {
        float: this.isRight ? 'none' : 'left',
        ...margin 
      }
    }
  },
  watch: {
    tagsList(val) {
      this.dynamicTags = this.tagsList.concat([])
    },
    dynamicTags(val) {
      this.$emit('change', val)
    }
  },
  methods: {
    handleClose(tag) {
      this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
      this.$emit('on-delete', tag, this.dynamicTags)
      this.$emit('update:tagsList', this.dynamicTags)
    },

    showInput() {
      this.$emit('on-addClick')
      this.inputVisible = true;
      this.$nextTick(() => {
        this.$refs.saveTagInput.$refs.input.focus();
      });
    },

    handleInputConfirm() {
      if (!this.inputValue.trim()) {
        this.inputVisible = false;
        this.inputValue = ''
        return
      }
      const inputValue = this.inputValue;
      const inputValues = this._devideInput(inputValue)
      if (this.validate && typeof this.validate === 'function') {
        this.validate(inputValues, this).then((res) => {
          if (res.length) {

            this.dynamicTags = this.isRight ? this.dynamicTags.concat(res) : res.concat(this.dynamicTags)
            this.$emit('on-add', res, this.dynamicTags)
            this.$emit('update:tagsList', this.dynamicTags)
          }
          this.inputVisible = false;
          this.inputValue = ''
        }).catch((err) => {
          if (this.invalidateHandle && typeof this.invalidateHandle === 'function') {
            this.invalidateHandle(err, this)
          }
        })
      } else {
        if (inputValues.length) {
          this.dynamicTags = this.isRight ? this.dynamicTags.concat(inputValues) : inputValues.concat(this.dynamicTags)
          this.$emit('on-add', inputValue, this.dynamicTags)
          this.$emit('update:tagsList', this.dynamicTags)
        }
        this.inputVisible = false;
        this.inputValue = ''
      }
    },
    _devideInput(inputValue) {
      const inputValuesMap = {}
      let start = 0
      for (let index = 0; index < inputValue.length; index++) {
        if (this.devides.includes(inputValue[index])) {
          start += 1 
        } else {
          if (inputValuesMap[start]) {
            inputValuesMap[start].push(inputValue[index])
          } else {
            inputValuesMap[start] = [inputValue[index]]
          }
        }
      }
      const inputValues = Object.values(inputValuesMap).reduce((total, value) => {
        const tag = value.join('').trim()
        if (!this.dynamicTags.includes(tag)) {
          total.push(tag)
        }
        return total
      }, [])
     
      // console.log('inputValues', inputValues) 
      return inputValues
    },
    setTags(data) {
      this.dynamicTags = data
      this.$emit('update:tagsList', this.dynamicTags)
    }
  },
  created() {
    
    if (this.tagsList.length) {
      this.dynamicTags = this.tagsList.concat([])
    }
  },
  mounted() {
  }
}
</script>