实现一个兼容eleUI form表单的多选组件

  本质上是实现了一个eleUI select组件中的创建条目功能的组件,仅仅是将dropdown的选择框变成了label形式。支持eleUI的form表单校验,同时组件也提供了组件内自定义校验的方法。常规的用eleUI校验表单只需要在rules中正常定义:

  

rules: FormRules = {
    labels: [
      { required: true, type: ‘array‘, message: ‘请选择标签‘, trigger: ‘change‘ },
      { required: true, type: ‘array‘, min: 3, max: 10,  message: ‘至少3个标签,最多添加10个标签‘, trigger: ‘change‘ },
    ],
  };

  eleUI表单校验的触发方式是组件内抛出一个emit事件(有change和on两种),在ElFormItem组件内会on监听这个事件,调用validate方法执行async-validator提供的校验。而form组件提供的this.$refs[formName].validate是通过遍历formItem调用每个ElFormItem组件内的validate方法。

  组件默认用了size=small时的大小。

<template>
  <div>
    <div
      class="input-content el-input--small"
      @click.stop="onFocus">
      <div ref="inputLabels" class="input-labels el-select__tags">
        <!-- disable-transitions 必须禁用动画效果 否则在计算高度时延迟动画时长触发 -->
        <el-tag
          v-for="(tag, i) in value"
          :key="tag"
          class="tag"
          size="mini"
          closable
          type="info"
          disable-transitions
          @close="onCloseTag(i)">
          {{ tag }}
        </el-tag>

        <!-- 输入用 -->
        <input
          ref="input"
          type="text"
          class="input-label-show el-select__input"
          v-model.trim="input"
          @focus="onFocus"
          @blur="isFocus = false"
          @click.stop
          @keydown.enter.prevent="onKeydownEnter" />
        <div
          v-if="max != 0"
          class="limit-content">
          {{ value.length }}/{{ max }}
        </div>
      </div>

      <!-- 展示用 -->
      <input
        type="text"
        class="el-input__inner"
        :class="[
          { ‘is-focus‘: isFocus },
          { ‘is-error‘: isError },
          { ‘is-success‘: isSuccess },
        ]"
        :style="{ ‘height‘: `${height}px` }"
        :placeholder="currentPlaceholder" />
      <!-- <div v-show="isError" class="el-form-item__error">{{ validateMessage }}</div> -->
    </div>

    <ul class="quick-selected-labels">
      <li
        v-for="tag in labelsToBeSelected"
        :key="tag"
        class="quick-label"
        @click="onClickAddLabel(tag)">
        <i class="quick-selected-icon el-icon-plus"></i>
        {{ tag }}
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit, Watch } from ‘vue-property-decorator‘;
import axios from ‘@/api/index‘;

@Component
export default class Labels extends Vue {
  @Prop({ type: Array, default: (): any => [] }) value: string[];
  @Prop({ type: Number, default: 0 }) min: number;
  @Prop({ type: Number, default: 0 }) max: number;
  @Prop(String) fieldId: string;  // 领域
  @Prop() initValue: any;

  input: string = ‘‘;
  currentPlaceholder: string = ‘回车添加标签 (最多添加10个)‘;
  isFocus: boolean = false;
  height: number = 32;  // 展示用input标签的高度
  quickSelectedLabels: string[] = [];  // 快速添加提供的标签
  isError: boolean = false;
  isSuccess: boolean = false;
  validateMessage: string = ‘‘;  // 校验不通过提示信息

  $refs: {
    input: HTMLInputElement,
    inputLabels: HTMLElement,
  }

  @Watch(‘value‘, { immediate: true, deep: true })
  onWatchValue(val: string[]) {
    if (val.length > 0 || this.input !== ‘‘) {
      this.currentPlaceholder = ‘‘;
      this.validate();
    } else {
      this.currentPlaceholder = ‘回车添加标签 (最多添加10个)‘;
    }
    this.resetInputHeight();
  }

  @Watch(‘input‘)
  onWatchInput(val: string) {
    if (this.value.length > 0 || this.input !== ‘‘) {
      this.currentPlaceholder = ‘‘;
    }  else {
      this.currentPlaceholder = ‘回车添加标签 (最多添加10个)‘;
    }
  }

  @Watch(‘fieldId‘, { immediate: true })
  onWatchField(val: string, oldVal: string) {
    if (val === ‘‘ ||val === oldVal) return;
    this.getQuickSelectedLabels(val);
    this.$emit(‘input‘, []);
  }

  created() {
    // this.getQuickSelectedLabels();
  }

  onFocus() {
    this.isFocus = true;
    this.$refs.input.focus();
  }

  /* 查询快速添加提供的标签 */
  getQuickSelectedLabels(fieldId: string = ‘‘) {
    this.quickSelectedLabels = [‘接口查询出的标签或者默认的标签‘];
  }

  /* 输入标签 */
  onKeydownEnter(e: any) {
    const val = this.input;
    if (val === ‘‘) {
      this.$message.warning(‘请勿输入空标签‘);
      return;
    }
    const labels = [...this.value];
    if (labels.includes(val)) {
      this.$message.warning(‘重复的标签‘);
      return;
    }
    this.input = ‘‘;
    labels.push(val);
    this.$emit(‘input‘, labels);
  }

  /* 删除标签 */
  @Emit(‘input‘)
  onCloseTag(i: number) {
    let labels = [...this.value];
    labels.splice(i, 1);
    return labels;
  }

  /* 添加标签 */
  @Emit(‘input‘)
  onClickAddLabel(label: string) {
    const labels = [...this.value];
    labels.push(label);
    return labels;
  }

  /* 计算快速选择的标签是否展示 */
  get labelsToBeSelected() {
    const tags: string[] = [];
    this.quickSelectedLabels.forEach((tag) => {
      if (!this.value.includes(tag)) {
        tags.push(tag);
      }
    });
    return tags;
  }

  /* 重置展示用input的高度 */
  resetInputHeight() {
    this.$nextTick(() => {
      const initHeight = 32;
      const dom = this.$refs.inputLabels;
      this.height = this.value.length === 0
        ? initHeight
        : Math.max(
          dom ? (dom.clientHeight + (dom.clientHeight > initHeight ? 4 : 0)) : 0,
          initHeight
        );
    });
  }

  /* elementUI 的 dispatch */
  dispatch(componentName: string, eventName: string, params: any[]) {
    let parent: any = this.$parent || this.$root;
    const options: any = parent.$options;
    let name = options.componentName;

    while (parent && (!name || name !== componentName)) {
      parent = parent.$parent;

      if (parent) {
        name = parent.$options.componentName;
      }
    }
    if (parent) {
      parent.$emit.apply(parent, [eventName].concat(params));
    }
  }

  /* 检验 */
  // @Emit(‘validate‘)
  validate() {
    this.dispatch(‘ElFormItem‘, ‘el.form.change‘, [this.value]);
    // const length = this.value.length;
    // const min = this.min;
    // const max = this.max;
    // if (length === 0) {
    //   this.validateMessage = ‘请选择标签‘;
    //   this.isError = true;
    //   this.isSuccess = false;
    //   return false;
    // } else if (min !== 0 && length < min) {
    //   this.validateMessage = `标签数量至少${min}个`;
    //   this.isError = true;
    //   this.isSuccess = false;
    //   return false;
    // } else if (max !== 0 && length > max) {
    //   this.validateMessage = `标签数量至多${max}个`;
    //   this.isError = true;
    //   this.isSuccess = false;
    //   return false;
    // }
    // this.isError = false;
    // this.isSuccess = true;
    // return true;
  }
}
</script>

<style>
.el-form-item.is-error .input-content .el-input__inner {
  border-color: #f56c6c !important;
}
</style>

<style lang="css" scoped>
  .input-content {
    position: relative;
    margin-bottom: 14px;
  }

  .input-content:hover .el-input__inner {
    border-color: #c0c4cc;
  }

  .input-content:hover .is-focus {
    border-color: #409eff;
  }

  .input-labels {
    padding-right: 45px;
    width: 100%;
    box-sizing: border-box;
  }

  .input-label-show {
    flex-grow: 1;
  }

  .input-info {
    font-size: 14px;
    color: #bbb;
    line-height: 14px;
  }

  .input-content .is-focus {
    border-color: #409eff;
  }

  .input-content .is-error {
    border-color: #f56c6c !important;
  }

  .is-success {
    border-color: #67c23a;
  }

  .tag {
    overflow: hidden;
    position: relative;
    margin-left: 4px;
    margin-right: 0;
    padding-right: 14px;
    max-width: 146px;
    min-width: 50px;
    font-size: 12px;
    color: #7e7e7e;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: rgba(239, 239, 239, .4);
    border-radius: 2px;
    box-sizing: border-box;
  }

  .tag:last-child {
    margin-right: 0;
  }

  .quick-selected-labels {
    overflow: hidden;
  }

  .quick-label {
    float: left;
    overflow: hidden;
    position: relative;
    margin: 0 10px 10px 0;
    padding: 8px 10px 8px 30px;
    max-width: 146px;
    min-width: 88px;
    height: 28px;
    font-size: 12px;
    color: #7e7e7e;
    line-height: 11px;
    text-overflow: ellipsis;
    white-space: nowrap;
    border: 1px solid #e9e9e9;
    border-radius: 2px;
    background-color: #fff;
    cursor: pointer;
    box-sizing: border-box;
  }

  .quick-label:hover {
    background-color: rgba(144, 147, 153, .1);
  }

  .quick-selected-icon {
    position: absolute;
    top: 8px;
    left: 10px;
    width: 12px;
    height: 12px;
    font-weight: 700;
    color: #bbb;
  }

  .limit-content {
    position: absolute;
    top: 8px;
    right: 10px;
    font-family: PingFangSC-Regular;
    font-size: 14px;
    color: #bbb;
    text-align: right;
    line-height: 14px;
  }
</style>

<style>
  .tag .el-tag__close {
    position: absolute;
    top: 2px;
    right: 0;
    font-size: 14px;
  }
</style>

原文地址:https://www.cnblogs.com/youyouluo/p/11975172.html

时间: 2024-10-31 05:38:09

实现一个兼容eleUI form表单的多选组件的相关文章

Form表单之复选框checkbox操作

input复选(checkbox): <label>input复选1组:</label> <input type="checkbox" name="checkbox1" value="checkbox复选1" checked="checked"/>checkbox复选1 <input type="checkbox" name="checkbox1"

写一个简单的form表单,当光标离开表单的时候表单的值发送给后台

1 <body> 2 <form action="index.php"> 3 <input type="text" name="txt" id="txt" value="abc"> 4 </form> 5 <script> 6 window.onload=function () { 7 var form=document.forms[0]; 8 v

及时从数据库中取得数据填放进Form表单的多选框中

#写上以下代码就不用担心数据库添加了数据而不能及时获取了 def __init__(self, *args, **kwargs): #每次创建Form1对象时执行init方法 super(Form1, self).__init__(*args, **kwargs) self.fields['book_type'] = forms.CharField( widget=forms.widgets.Select(choices=models.BookType.objects.values_list('

form表单那点事儿(下) 进阶篇

上一篇主要温习了一下form表单的属性和表单元素,这一片主要讲解用JavaScript如何操作form. 表单操作 取值 用JavaScript操作表单,免不了会有取值赋值操作,比如有以下表单: <form id='form0'></form> <form action="/login" method="post" target="blank" id='form1'> <input type="

deirective写form表单组件

directive 在使用隔离 scope 的时候,提供了三种方法同隔离之外的地方交互.这三种分别是 @ 绑定一个局部 scope 属性到当前 dom 节点的属性值.结果总是一个字符串,因为 dom 属性是字符串.& 提供一种方式执行一个表达式在父 scope 的上下文中.如果没有指定 attr 名称,则属性名称为相同的本地名称.= 通过 directive 的 attr 属性的值在局部 scope 的属性和父 scope 属性名之间建立双向绑定 但是当我们不使用隔离scope的时候,我们要能够

用jquery写自己的form表单验证

这几天看了锋利的jquery,感觉很不错.特别是jquery强大的选择器.今天就利用jquery写了一个自己的form表单验证的小案例.当巩固下jquery.首先贴下代码,当然只是一个小案例. 思路:   1.<input type="text" Validate="Date" id="date"/>这里的 Validate:是我们需要验证的类型(属于日期类型),这里你也可以自己定义.id属性就不用说了.<input type=

form表单里如果只存在一个文本框,enter键提交

在这里说一说浏览器里form表单的默认行为 我们都知道浏览器是存在很多默认行为的,可能是出于常用行为考虑又或者是历史原因.但有时候我们不需要这些默认行为.以下: 1).当form表单里只存在一个input输入框时,回车会提交表单操作.  解决方法可以在form里面再加入一个隐藏的input输入框,或者把input从form里面放出来. 2).当form表单里有一个type=”submit”的按钮,回车会自动提交. 3).当form表单里的button按钮没有加type类型时,在ie下默认是but

HTML中的form表单有一个关键属性 enctype

HTML中的form表单有一个关键属性 enctype=application/x-www-form-urlencoded 或multipart/form-data. 1.enctype="application/x-www-form-urlencoded"是默认的编码方式,当以这种方式提交数据时,HTTP报文中的内容是: Html代码   <span style="font-size: small;">POST /post_test.php HTTP/

jqueryEasyUI form表单提交的一个困惑

今天用到了jqueryEasyUI的form表单做一个增加操作的提交,想打开调试(用的是火狐)看看传的参数,但是怎么也看不到form表单提交的http请求?而且还会发送一个另外的请求! 在页面加载时,会首先初始化一个datagrid,然后可以选择是否选择添加操作.另外发送的这个请求,就是这个datagrid想后台请求数据的请求.就相当与重新刷新了datagrid. datagrid请求数据库数据的操作写在$(function(){})里面,后来想想,是不是这个原因,jqueryEasyUI的fo