Element UI 源码改造 —— 自定义数字输入框的实现

简介: Element UI 源码改造 —— 自定义数字输入框的实现

需求描述

表单中需要一个数字输入框,功能与 el-input-number 基本一样,但有以下区别:

1. 按上下方向键时,不改变输入的内容

2. 输入的值超出最大值或小于最小值时,保留输入的值,而不是当失去焦点时自动替换为最大或最小值

3. 像el-input一样,可以配置可清空按钮

实现方案

复制一份Element UI源码中的el-input-number对应的文件,改造成满足需求的组件。

完整代码范例

1. 找到 Element UI源码中的el-input-number对应的文件

node_modules/element-ui/packages/input-number/src/input-number.vue

2. 新建文件 newElNumberInput.vue

将源码input-number.vue 拷贝到 newElNumberInput.vue中,并按需求对源码进行修改,最终代码如下:

<template>
    <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"
                :clearable = 'clearable'
                :value="displayValue"
                :placeholder="placeholder"
                :disabled="inputNumberDisabled"
                :size="inputNumberSize"
                :max="max"
                :min="min"
                :name="name"
                :label="label"
                @blur="handleBlur"
                @focus="handleFocus"
                @input="handleInput"
                @change="handleInputChange">
        </el-input>
    </div>
</template>
<script>
    import ElInput from 'element-ui/packages/input';
    import Focus from 'element-ui/src/mixins/focus';
    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: {
            clearable:Boolean,
            step: {
                type: Number,
                default: 1
            },
            stepStrictly: {
                type: Boolean,
                default: false
            },
            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,
            placeholder: String,
            precision: {
                type: Number,
                validator(val) {
                    return val >= 0 && val === parseInt(val, 10);
                }
            }
        },
        data() {
            return {
                currentValue: 0,
                userInput: null
            };
        },
        watch: {
            value: {
                immediate: true,
                handler(value) {
                    let newVal = value === undefined ? value : Number(value);
                    if (newVal !== undefined) {
                        if (isNaN(newVal)) {
                            return;
                        }
                        if (this.stepStrictly) {
                            const stepPrecision = this.getPrecision(this.step);
                            const precisionFactor = Math.pow(10, stepPrecision);
                            newVal = Math.round(newVal / this.step) * precisionFactor * this.step / precisionFactor;
                        }
                        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.userInput = null;
                    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() {
                const {value, step, getPrecision, precision} = this;
                const stepPrecision = getPrecision(step);
                if (precision !== undefined) {
                    if (stepPrecision > precision) {
                        console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step');
                    }
                    return precision;
                } else {
                    return Math.max(getPrecision(value), stepPrecision);
                }
            },
            controlsAtRight() {
                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;
            },
            displayValue() {
                if (this.userInput !== null) {
                    return this.userInput;
                }
                let currentValue = this.currentValue;
                if (typeof currentValue === 'number') {
                    if (this.stepStrictly) {
                        const stepPrecision = this.getPrecision(this.step);
                        const precisionFactor = Math.pow(10, stepPrecision);
                        currentValue = Math.round(currentValue / this.step) * precisionFactor * this.step / precisionFactor;
                    }
                    if (this.precision !== undefined) {
                        currentValue = currentValue.toFixed(this.precision);
                    }
                }
                return currentValue;
            }
        },
        methods: {
            toPrecision(num, precision) {
                if (precision === undefined) precision = this.numPrecision;
                return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision));
            },
            getPrecision(value) {
                if (value === undefined) return 0;
                const valueString = value.toString();
                const dotPosition = valueString.indexOf('.');
                let precision = 0;
                if (dotPosition !== -1) {
                    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);
                // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
                return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor);
            },
            _decrease(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);
            },
            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);
            },
            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) return;
                this.userInput = null;
                this.$emit('input', newVal);
                this.$emit('change', newVal, oldVal);
                this.currentValue = newVal;
            },
            handleInput(value) {
                this.userInput = value;
            },
            handleInputChange(value) {
                const newVal = value === '' ? undefined : Number(value);
                if (!isNaN(newVal) || value === '') {
                    this.setCurrentValue(newVal);
                } else {
                    this.setCurrentValue(undefined)
                }
                this.userInput = null;
            },
            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>


3. 在目标文件中引入使用

<template>
    <div style="padding: 30px">
        <el-form ref="formRef" :model="formData" size="mini" label-width="100px">
            <el-form-item label="分数" prop='score'
                          :rules="[
                           { required: true, message: '不能为空', trigger: 'blur' },
                           {type: 'number', min:0,max:99,message: '请输入0-99之间的数字', trigger: 'blur' }
                            ]"
            >
                <NewElNumberInput clearable :controls="false" :max="99" v-model.number="formData.score"/>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="submit">提交</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>
<script>
    import NewElNumberInput from './newElNumberInput'
 
    export default {
        components: {NewElNumberInput},
        data() {
            return {
                formData: {}
            }
        },
        methods: {
            submit() {
                this.$refs.formRef.validate((valid) => {
                    if (valid) {
                        alert('通过表单校验!');
                    } else {
                        console.log('表单校验失败!');
                        return false;
                    }
                });
            }
        }
    }
</script>
<style scoped>
</style>

4. 最终效果

友情提示

若有其他通过改造 Element UI 源码即可快速实现的功能,都可以参考本例实现。

目录
相关文章
|
2天前
Element UI【级联选择器】el-cascader 获取选中内容的 label 数据,鼠标悬浮显示超长内容
Element UI【级联选择器】el-cascader 获取选中内容的 label 数据,鼠标悬浮显示超长内容
12 3
|
1天前
|
数据安全/隐私保护
Element UI 密码输入框--可切换显示隐藏,自定义图标
Element UI 密码输入框--可切换显示隐藏,自定义图标
6 0
|
1天前
Element UI 表格【列宽自适应】
Element UI 表格【列宽自适应】
4 0
|
1天前
|
前端开发
ElementPlus卡片如何能够一行呈四,黑马UI前端布局视频资料,element样式具体的细节无法修改,F12找到那个位置,可能在其他组件写了错误,找到那个位置,围绕着位置解决问题最快了,卡片下边
ElementPlus卡片如何能够一行呈四,黑马UI前端布局视频资料,element样式具体的细节无法修改,F12找到那个位置,可能在其他组件写了错误,找到那个位置,围绕着位置解决问题最快了,卡片下边
|
1天前
Element UI 【表格合计】el-table 实战范例 -- 添加单位,自定义计算逻辑
Element UI 【表格合计】el-table 实战范例 -- 添加单位,自定义计算逻辑
6 0
|
2月前
|
前端开发 搜索推荐 开发者
SAP UI5 sap.m.Column 控件的 minScreenWidth 属性介绍
SAP UI5 sap.m.Column 控件的 minScreenWidth 属性介绍
|
2月前
|
JavaScript 前端开发 开发者
SAP UI5 控件 sap.m.ListBase 的 inset 属性的作用介绍
SAP UI5 控件 sap.m.ListBase 的 inset 属性的作用介绍
|
2月前
|
前端开发 JavaScript API
SAP UI5 sap.ui.require.toUrl 的作用介绍
SAP UI5 sap.ui.require.toUrl 的作用介绍
|
2月前
|
JSON 前端开发 测试技术
SAP UI5 sap.ui.core.util.MockServer.simulate 方法介绍
SAP UI5 sap.ui.core.util.MockServer.simulate 方法介绍
使用 SAP UI5 Event Bus 机制,修复 SAP UI5 分页显示数据的一个 bug 试读版
使用 SAP UI5 Event Bus 机制,修复 SAP UI5 分页显示数据的一个 bug 试读版

相关实验场景

更多