<template> <!--@dragstart.prevent禁止input中数字的拖动--> <div @dragstart.prevent :class="[ 'el-input-number', inputNumberSize ? 'el-input-number--' + inputNumberSize : '', { 'is-disabled': inputNumberDisabled }, { 'is-without-controls': !controls }, { 'is-controls-right': controlsAtRight } ]"> <span class="el-input-number__decrease" role="button" v-if="controls" v-repeat-click="decrease" :class="{'is-disabled': minDisabled}" @keydown.enter="decrease"> <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i> </span> <span class="el-input-number__increase" role="button" v-if="controls" v-repeat-click="increase" :class="{'is-disabled': maxDisabled}" @keydown.enter="increase"> <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i> </span> <el-input ref="input" :value="currentInputValue" :placeholder="placeholder" :disabled="inputNumberDisabled" :size="inputNumberSize" :max="max" :min="min" :name="name" :label="label" @keydown.up.native.prevent="increase" @keydown.down.native.prevent="decrease" @blur="handleBlur" @focus="handleFocus" @change="handleInputChange"> </el-input> </div> </template> <script> import ElInput from 'element-ui/packages/input'; import Focus from 'element-ui/src/mixins/focus'; //RepeatClick,用来控制左键按下时不断触发事件 import RepeatClick from 'element-ui/src/directives/repeat-click'; export default { name: 'ElInputNumber', mixins: [Focus('input')], inject: { elForm: { default: '' }, elFormItem: { default: '' } }, directives: { repeatClick: RepeatClick }, components: { ElInput }, props: { step: { //计数器步长 type: Number, default: 1 }, max: { //设置计数器容许的最大值 type: Number, default: Infinity }, min: { //设置计数器容许的最小值 type: Number, default: -Infinity }, value: {}, //绑定值 disabled: Boolean, //是否禁用计数器 size: String, //计数器尺寸 controls: { //是否使用控制按钮 type: Boolean, default: true }, controlsPosition: { //控制按钮位置 type: String, default: '' }, name: String, //原生属性 label: String, //输入框关联的label文字 placeholder: String, //输入框默认 placeholder precision: { //数值精度 type: Number, validator(val) { return val >= 0 && val === parseInt(val, 10); } } }, data() { return { currentValue: 0 }; }, watch: { value: { //确认是否以当前的初始值执行handler的函数。 immediate: true, handler(value) { //Number() 函数把对象的值转换为数字。 let newVal = value === undefined ? value : Number(value); if (newVal !== undefined) { if (isNaN(newVal)) { return; } if (this.precision !== undefined) { //若是数值精度存在,将数字按精度转换 newVal = this.toPrecision(newVal, this.precision); } } if (newVal >= this.max) newVal = this.max; if (newVal <= this.min) newVal = this.min; this.currentValue = newVal; this.$emit('input', newVal); } } }, computed: { // 返回当前减号是否被禁用 minDisabled() { // 当前值-计数器步长<最小值时,减号被禁用,不能再继续减 return this._decrease(this.value, this.step) < this.min; }, maxDisabled() { return this._increase(this.value, this.step) > this.max; }, //返回数值的精度 numPrecision() { // precision 的值必须是一个非负整数,而且不能小于 step 的小数位数。 const { value, step, getPrecision, precision } = this; const stepPrecision = getPrecision(step); if (precision !== undefined) { //若是step 的小数位数大于数值精度时,控制台输出警告并返回数值精度 if (stepPrecision > precision) { console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step'); } return precision; } else { //若是step 的小数位数小于数值精度时,再比较数值的精度和step的精度,取最大值 return Math.max(getPrecision(value), stepPrecision); } }, // 控制按钮的位置 controlsAtRight() { // 当控制按钮存在,而且控制按钮的位置为right时,此处经过添加is-controls-right类来改变控制按钮的位置,使控制按钮在右边显示。 return this.controls && this.controlsPosition === 'right'; }, _elFormItemSize() { return (this.elFormItem || {}).elFormItemSize; }, //计数器的大小 inputNumberSize() { return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; }, // 是否禁用计数器 inputNumberDisabled() { return this.disabled || (this.elForm || {}).disabled; }, currentInputValue() { const currentValue = this.currentValue; if (typeof currentValue === 'number' && this.precision !== undefined) { return currentValue.toFixed(this.precision); } else { return currentValue; } } }, methods: { //按精度转换数值 toPrecision(num, precision) { if (precision === undefined) precision = this.numPrecision; //toFixed() 方法可把 Number 四舍五入为指定小数位数的数字,返回字符串;parseFloat()函数可解析一个字符串,并返回一个浮点数。 return parseFloat(parseFloat(Number(num).toFixed(precision))); }, //获取value的小数位数 getPrecision(value) { if (value === undefined) return 0; const valueString = value.toString(); const dotPosition = valueString.indexOf('.'); let precision = 0; if (dotPosition !== -1) { //valueString.length减去小数点前面的位数,剩下的就是小数点后面的位数 precision = valueString.length - dotPosition - 1; } return precision; }, _increase(val, step) { if (typeof val !== 'number' && val !== undefined) return this.currentValue; const precisionFactor = Math.pow(10, this.numPrecision); return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor); }, //返回value减去step后的值 _decrease(val, step) { if (typeof val !== 'number' && val !== undefined) return this.currentValue; //Math.pow()计算10的this.numPrecision次方 const precisionFactor = Math.pow(10, this.numPrecision); //这里主要是为了减小偏差 return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor); }, increase() { if (this.inputNumberDisabled || this.maxDisabled) return; const value = this.value || 0; const newVal = this._increase(value, this.step); this.setCurrentValue(newVal); }, //点击减号时触发的事件 decrease() { if (this.inputNumberDisabled || this.minDisabled) return; const value = this.value || 0; const newVal = this._decrease(value, this.step); this.setCurrentValue(newVal); }, handleBlur(event) { this.$emit('blur', event); this.$refs.input.setCurrentValue(this.currentInputValue); }, handleFocus(event) { this.$emit('focus', event); }, setCurrentValue(newVal) { const oldVal = this.currentValue; if (typeof newVal === 'number' && this.precision !== undefined) { newVal = this.toPrecision(newVal, this.precision); } if (newVal >= this.max) newVal = this.max; if (newVal <= this.min) newVal = this.min; if (oldVal === newVal) { //改变input的当前值 this.$refs.input.setCurrentValue(this.currentInputValue); return; } this.$emit('input', newVal); this.$emit('change', newVal, oldVal); this.currentValue = newVal; }, handleInputChange(value) { const newVal = value === '' ? undefined : Number(value); if (!isNaN(newVal) || value === '') { this.setCurrentValue(newVal); } }, select() { this.$refs.input.select(); } }, mounted() { let innerInput = this.$refs.input.$refs.input; innerInput.setAttribute('role', 'spinbutton'); innerInput.setAttribute('aria-valuemax', this.max); innerInput.setAttribute('aria-valuemin', this.min); innerInput.setAttribute('aria-valuenow', this.currentValue); innerInput.setAttribute('aria-disabled', this.inputNumberDisabled); }, updated() { if (!this.$refs || !this.$refs.input) return; const innerInput = this.$refs.input.$refs.input; innerInput.setAttribute('aria-valuenow', this.currentValue); } }; </script>
解析:
(1)先看下html结构javascript
<div class="el-input-number"> <!--左边的减号--> <span class="el-input-number__decrease"> <i class="el-icon-minus"></i> </span> <!--右边的加号--> <span class="el-input-number__increase"> <i class="el-icon-plus"></i> </span> <!--中间的输入框--> <el-input ref="input"></el-input> </div>
左边的减号和右边的加号是经过绝对定位,设置在input左右的padding位置的,input的css代码以下:css
.el-input-number .el-input__inner { -webkit-appearance: none; padding-left: 50px; padding-right: 50px; text-align: center; }
这个inputNumber源码还算简单,多看几遍就懂了html
<template> <div class="el-card" :class="shadow ? 'is-' + shadow + '-shadow' : 'is-always-shadow'"> <!--头部:设置 header,也能够经过 slot#header 传入 DOM--> <div class="el-card__header" v-if="$slots.header || header"> <slot name="header">{{ header }}</slot> </div> <!--内容部分--> <div class="el-card__body" :style="bodyStyle"> <slot></slot> </div> </div> </template> <script> export default { name: 'ElCard', props: { header: {}, //设置 header,也能够经过 slot#header 传入DOM bodyStyle: {}, //设置 body 的样式 shadow: { //设置阴影显示时机 type: String } } }; </script>
<template> <span class="el-breadcrumb__item"> <span :class="['el-breadcrumb__inner', to ? 'is-link' : '']" ref="link" role="link"> <!--插入文字--> <slot></slot> </span> <!--图标分隔符--> <i v-if="separatorClass" class="el-breadcrumb__separator" :class="separatorClass"></i> <!--分隔符--> <span v-else class="el-breadcrumb__separator" role="presentation">{{separator}}</span> </span> </template> <script> export default { name: 'ElBreadcrumbItem', props: { to: {}, //路由跳转对象,同 vue-router 的 to replace: Boolean //在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录 }, data() { return { separator: '', separatorClass: '' }; }, inject: ['elBreadcrumb'], mounted() { //获取父组件的separator this.separator = this.elBreadcrumb.separator; //获取父组件的separatorClass this.separatorClass = this.elBreadcrumb.separatorClass; const link = this.$refs.link; link.setAttribute('role', 'link'); //添加点击事件 link.addEventListener('click', _ => { const { to, $router } = this; if (!to || !$router) return; //根据replace的值肯定是replace仍是push,replace 将不会向 history 添加新记录 this.replace ? $router.replace(to) : $router.push(to); }); } }; </script>