网站首页 » 前端开发 » Vue » Vue2 Element 模仿秀-输入框篇(Input)
上一篇:
下一篇:

Vue2 Element 模仿秀-输入框篇(Input)

前方

这里文章主要分享Element 对 input 输入框在组件封装。但是本文没有把关于 input 的输入框所有组件都模仿一遍,原因有二:1、官方介绍 input 类组件中包括了其它组件相对较复杂的组件,比如:select 组件、autocomplete 组件,在这里我打算把它分开来模仿。2、如果都放在一篇文章中,代码会很多,不便于阅读理解。

文件结构
├── index.html
├── mixins
│  └── emitter.js
├── utils
│  ├── calcTextareaHeight.js
│  └── merge.js
├── assets
│  ├── fonts
│  │  ├── yunkus-icons.ttf
│  │  └── yunksu-icons.woff
│  └── icon.css
├── main.js
├── router
│  └── index.js      # 路由配置文件
└── components
   ├── Home.vue          # 大的框架结构组件
   └── Input.vue

下面就是三个主要的文件代码,一个是 Hom.vue 另一个是 Input.vue ,还有一个 merge.js。其它两个文件的代码就没有把它们帖出来了,不过已经在文章最后面把它们的链接帖出来。

效果演示:

Vue Element 模仿秀-输入框篇(Input)

第一个 textarea 是默认 textarea,第二个是默认一行,多行自适应高度,第三个就是设置了一个区间,在这里区间内 textarea 为自适应,超过这 textarea 的最大高度时,就会出现滚动条。

Home.vue
<template>
<div class="container demo-input">
  <div class="panel panel-default">
    <div class="panel-heading">基本用法</div>
    <div class="panel-body">
        <yk-input v-model="input1" placeholder="请输入内容" clearable></yk-input>
    </div>
  </div>

  <div class="panel panel-default">
    <div class="panel-heading">禁用状态</div>
    <div class="panel-body">
        <yk-input v-model="input1" disabled placeholder="请输入内容"></yk-input>
    </div>
  </div>
 
  <div class="panel panel-default">
    <div class="panel-heading">属性方式</div>
    <div class="panel-body">
         <yk-input v-model="input2" prefix-icon="yk-icon-search" placeholder="请输入日期"></yk-input>
         <yk-input v-model="input2" suffix-icon="yk-icon-date" placeholder="请输入日期"></yk-input>
    </div>
  </div>
  
  <div class="panel panel-default">
    <div class="panel-heading">slot 方式</div>
    <div class="panel-body">
        <yk-input v-model="input2" placeholder="请输入日期"><i slot="prefix" class="yk-input-icon yk-icon-search"></i></yk-input>
        <yk-input v-model="input3" placeholder="请输入日期"><i slot="suffix" class="yk-input-icon yk-icon-date"></i></yk-input>
    </div>
  </div>
   
  <div class="panel panel-default">
    <div class="panel-heading">文本域</div>
    <div class="panel-body">
       <yk-input type="textarea" :rows="2" placeholder="请输入内容" v-model="textarea"></yk-input>
    </div>
  </div>

  <div class="panel panel-default">
    <div class="panel-heading">文本域(autosize)</div>
    <div class="panel-body">
       <yk-input type="textarea" placeholder="请输入内容" autosize v-model="textarea2"></yk-input>
    </div>
  </div>

  <div class="panel panel-default">
    <div class="panel-heading">文本域(:autosize="{ minRows: 2, maxRows: 4}")</div>
    <div class="panel-body">
       <yk-input type="textarea" placeholder="请输入内容" :autosize="{ minRows: 2, maxRows: 4}" v-model="textarea3"></yk-input>
    </div>
  </div>

  <div class="panel panel-default">
      <div class="panel-heading">复合型输入框</div>
      <div class="panel-body">
        <div>
            <yk-input placeholder="请输入内容" v-model="input8">
              <template slot="prepend">Http://</template>
            </yk-input>
          </div>
          <div style="margin-top: 15px;">
            <yk-input placeholder="请输入内容" v-model="input9">
              <template slot="append">.com</template>
            </yk-input>
          </div>
      </div>
    </div>

  <div class="panel panel-default">
    <div class="panel-heading">输入框尺寸</div>
    <div class="panel-body">
      <div class="demo-input-size">
        <yk-input
          placeholder="请输入内容"
          suffix-icon="yk-icon-date"
          v-model="input4">
        </yk-input>
        <yk-input
          size="medium"
          placeholder="请输入内容"
          suffix-icon="yk-icon-date"
          v-model="input5">
        </yk-input>
        <yk-input
          size="small"
          placeholder="请输入内容"
          suffix-icon="yk-icon-date"
          v-model="input6">
        </yk-input>
        <yk-input
          size="mini"
          placeholder="请输入内容"
          suffix-icon="yk-icon-date"
          v-model="input7">
        </yk-input>
      </div>
    </div>
  </div>
 </div> 
</template>
<script>
import YkInput from "@/components/Input";
import "@/assets/icon.css";
export default {
  name: "Home",
  components: { YkInput },
  data() {
    return {
      input1: "",
      input2: "",
      input3: "",
      input4: "",
      input5: "",
      input6: "",
      input7: "",
      input8: "",
      input9: "",
      textarea: "",
      textarea2: "",
      textarea3: ""
    };
  }
};
</script>
Input.vue
<template>
       <!-- v-bind="$props" 绑定 props 中的所有属性 -->
   <div :class="[
    type === 'textarea'?'yk-textarea-box':'yk-input-box',
    size ? 'yk-input-' + size : '',
    {'is-disabled': inputDisabled,
    'yk-input-group-box': $slots.prepend || $slots.append, // 如果 $slots.prepend 或者 $slots.append 有值
    'yk-input-group-append-box': $slots.append,
    'yk-input-group-prepend-box': $slots.prepend,
     'yk-input-box-prefix': $slots.prefix || prefixIcon,
     'yk-input-box-suffix': $slots.suffix || suffixIcon
    }
   ]"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
   >
  <template v-if="type !== 'textarea'">
    <!-- 前置元素 -->
      <div class="yk-input-group-prepend" v-if="$slots.prepend">
        <slot name="prepend"></slot>
      </div>
    <input
    class="yk-input"
    @input="handleInput"
    @change="handleChange"
    v-bind="$props" 
    :value="currentValue" 
    type="text"
    :disabled="inputDisabled">
    <!-- 前置内容 -->
    <span class="yk-input-prefix" v-if="$slots.prefix || prefixIcon">
      <slot name="prefix"></slot>
      <i class="yk-input-icon"
          v-if="prefixIcon"
          :class="prefixIcon">
      </i>
    </span>
    <!-- 后置内容 -->
    <span class="yk-input-suffix" v-if="$slots.suffix || showClear || suffixIcon">
       <span class="yk-input-suffix-inner">
         <template v-if="!showClear">
            <slot name="suffix"></slot>
            <i class="yk-input-icon"
              v-if="suffixIcon"
              :class="suffixIcon">
            </i>
          </template>
          <i v-else class="yk-input-icon yk-icon-circle-close yk-input-clear" @click="clear"></i>
       </span>
    </span>
    <!-- 后置元素 -->
      <div class="yk-input-group-append" v-if="$slots.append">
        <slot name="append"></slot>
      </div>
  </template>
  <textarea v-else
  :style="textareaStyle"
    class="yk-textarea"
    :value="currentValue"
    v-bind="$props"
    ref="textarea"
    :disabled="inputDisabled"
    @input="handleInput"
    @change="handleChange"
  ></textarea>
   </div>
</template>
<script>
import Emitter from "@/mixins/emitter";
// calcTextareaHeight 模块实现 textarea 高度自适应
import calcTextareaHeight from "@/utils/calcTextareaHeight";
// merge 模块实现了简单的对象合并
import merge from "@/utils/merge";
export default {
  name: "YkInput",
  componentName: "YkInput",
  mixins: [Emitter],
  props: {
    value: String,
    type: {
      type: String,
      default: "text"
    },
    placeholder: String,
    disabled: Boolean,
    clearable: {
      type: Boolean,
      default: false
    },
    suffixIcon: String,
    prefixIcon: String,
    size: String,
    resize: String,
    autosize: {
      type: [Boolean, Object],
      default: false
    }
  },
  data() {
    return {
      // 初始化当前值
      currentValue: this.value,
      hovering: false, // 这个属性到于鼠标悬浮时显示一些元素
      focused: false,
      textareaCalcStyle: {} // 这个用于保存 textarea 的一些属性,比如最小高度,高度
    };
  },
  methods: {
    handleInput(event) {
      const value = event.target.value;
      this.$emit("input", value);
      this.setCurrentValue(value);
    },
    handleChange(event) {
      this.$emit("change", event.target.value);
    },
    setCurrentValue(value) {
      this.currentValue = value;
      // this.$nextTick(_ => {
      this.resizeTextarea();
      // });
    },
    clear() {
      this.$emit("input", "");
      this.$emit("change", "");
      this.setCurrentValue("");
      this.focus();
    },
    resizeTextarea() {
      // if (this.$isServer) return;
      const { autosize, type } = this;
      // 如果不是 textarea 直接返回,不再执行后面的代码
      if (type !== "textarea") return;
      // 如果没有设置autosize
      if (!autosize) {
        this.textareaCalcStyle = {
          // 求出 textarea 的最小高度
          minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
        };
        return;
      }

      // 如果存在 autosize 则会继续往下执行
      const minRows = autosize.minRows;
      const maxRows = autosize.maxRows;

      // 通过 calcTextareaHeight 来计算出 textarea 的最小高度以及最大高度
      this.textareaCalcStyle = calcTextareaHeight(
        this.$refs.textarea,
        minRows,
        maxRows
      );
    }
  },
  computed: {
    textareaStyle() {
      // 合并样式
      return merge({}, this.textareaCalcStyle, { resize: this.resize });
    },
    inputDisabled() {
      return this.disabled || false;
    },
    showClear() {
      return (
        this.clearable &&
        this.currentValue !== "" &&
        (this.focused || this.hovering)
      );
    }
  },
  mounted() {
    this.resizeTextarea();
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scope>
.demo-input .yk-input-box {
  margin-bottom: 15px;
}
.demo-input .yk-input-box {
  width: 180px;
  margin-right: 15px;
}
.demo-input .yk-textarea-box {
  width: 414px;
}
.demo-input .yk-input-group-box {
  width: 100%;
}
.yk-input-box {
  position: relative;
  font-size: 14px;
  display: inline-block;
  width: 100%;
}
.yk-input {
  -webkit-appearance: none;
  background-color: #fff;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  font-size: inherit;
  height: 40px;
  line-height: 1;
  outline: 0;
  padding: 0 15px;
  -webkit-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  width: 100%;
}
.yk-input-box.is-active .yk-input,
.yk-input:focus {
  border-color: #409bc2;
  outline: 0;
}
.yk-input-box.is-disabled .yk-input {
  background-color: #f5f7fa;
  border-color: #e4e7ed;
  color: #c0c4cc;
  cursor: not-allowed;
}
.yk-input-box.is-disabled .yk-input::-webkit-input-placeholder {
  color: #ccc;
}
.yk-input-box .yk-input:hover {
  border-color: #c0c4cc;
}
.yk-input-box.is-disabled .yk-input:hover {
  border-color: #dcdfe6;
}
.yk-input-suffix {
  right: 5px;
  -webkit-transition: all 0.3s;
  transition: all 0.3s;
  pointer-events: none;
}
.yk-input-prefix,
.yk-input-suffix {
  position: absolute;
  top: 0;
  color: #c0c4cc;
  height: 100%;
  text-align: center;
  -webkit-transition: all 0.3s;
  transition: all 0.3s;
}
.yk-input-box .yk-input-clear {
  color: #c0c4cc;
  font-size: 14px;
  line-height: 16px;
  cursor: pointer;
  -webkit-transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.yk-input-box .yk-input-clear:hover {
  color: #909399;
}
.yk-input-suffix-inner {
  pointer-events: all;
}
.yk-input-prefix {
  left: 5px;
  transition: all 0.3s;
}
.yk-input-icon {
  height: 100%;
  width: 25px;
  text-align: center;
  -webkit-transition: all 0.3s;
  transition: all 0.3s;
  line-height: 40px;
}
.yk-input-icon:after {
  content: "";
  height: 100%;
  width: 0;
  display: inline-block;
  vertical-align: middle;
}
.yk-input-prefix {
  position: absolute;
  left: 5px;
  top: 0;
  color: #c0c4cc;
}
.yk-input-box-prefix .yk-input {
  padding-left: 30px;
}
.yk-textarea-box {
  display: inline-block;
  width: 100%;
  vertical-align: bottom;
  font-size: 14px;
}
.yk-textarea {
  display: block;
  resize: vertical;
  padding: 5px 15px;
  line-height: 1.5;
  box-sizing: border-box;
  width: 100%;
  font-size: inherit;
  color: #606266;
  background-color: #fff;
  background-image: none;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.yk-input-group-box {
  line-height: normal;
  display: inline-table;
  width: 100%;
  border-collapse: separate;
}
.yk-input-group-box > .yk-input {
  vertical-align: middle;
  display: table-cell;
}
.yk-input-group-append,
.yk-input-group-prepend {
  background-color: #f5f7fa;
  color: #909399;
  vertical-align: middle;
  display: table-cell;
  position: relative;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  padding: 0 20px;
  width: 1px;
  white-space: nowrap;
}
.yk-input-group-prepend {
  border-right: 0;
}
.yk-input-group-append {
  border-left: 0;
}
.yk-input-group-append-box .yk-input,
.yk-input-group-prepend {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.yk-input-group-prepend-box .yk-input,
.yk-input-group-append {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.yk-input-medium {
  font-size: 14px;
}
.yk-input-small {
  font-size: 13px;
}
.yk-input-mini {
  font-size: 12px;
}
.yk-input-medium .yk-input {
  height: 36px;
}
.yk-input-small .yk-input {
  height: 32px;
}
.yk-input-mini .yk-input {
  height: 28px;
}
.yk-input-medium .yk-input-icon {
  line-height: 36px;
}
.yk-input-small .yk-input-icon {
  line-height: 32px;
}
.yk-input-mini .yk-input-icon {
  line-height: 28px;
}
</style>

Input 组件中使用到的 merge 模块中的代码非常地简单:

merge.js
export default function (target) {
  console.log(arguments[2]);
  for (let i = 1, j = arguments.length; i < j; i++) {
    let source = arguments[i] || {};
    for (let prop in source) {
      if (source.hasOwnProperty(prop)) {
        let value = source[prop];
        if (value !== undefined) {
          target[prop] = value;
        }
      }
    }
  }
  return target;
};

还有两个文件一个是 emitter ,另一个是 calcTextareaHeight ,这两个模板我已经单独到两篇文章中了,你可以前往阅读:《Vue Element emitter.js 详解》、《Vue Element calcTextareaHeight.js 详解》。

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/vue-element-parody-input/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论 END