Browse Source

refactor: inputnumber com

richard1015 4 years ago
parent
commit
304204eebf

+ 47 - 72
src/packages/inputnumber/demo.vue

@@ -2,113 +2,88 @@
   <div class="demo">
     <h2>基本用法</h2>
     <nut-cell>
-      <nut-inputnumber
-        v-model:modelValue="state.val1"
-        @change="change"
-        @blur="blur"
-        @focus="focus"
-      />
+      <nut-inputnumber v-model="state.val1" />
     </nut-cell>
     <h2>步长设置</h2>
     <nut-cell>
-      <nut-inputnumber
-        v-model:modelValue="state.val2"
-        :step="state.step"
-        :decimal-places="1"
-      />
+      <nut-inputnumber v-model="state.val2" step="5" />
     </nut-cell>
     <h2>限制输入范围</h2>
     <nut-cell>
       <nut-inputnumber
-        v-model:modelValue="state.val3"
-        :min="state.min"
-        :max="state.max"
-        @reduce-no-allow="reduceNoAllow"
-        @add-no-allow="addNoAllow"
+        v-model="state.val3"
+        @overlimit="overlimit"
+        min="10"
+        max="20"
       />
     </nut-cell>
-    <h2>禁用操作&输入框</h2>
+    <h2>禁用操作</h2>
+    <nut-cell>
+      <nut-inputnumber v-model="state.val4" disabled />
+    </nut-cell>
+    <h2>只读禁用输入框</h2>
+    <nut-cell>
+      <nut-inputnumber v-model="state.val5" readonly />
+    </nut-cell>
+    <h2>支持小数</h2>
     <nut-cell>
       <nut-inputnumber
-        :readonly="true"
-        v-model:modelValue="state.val4"
-        min="0"
-        max="0"
-        @focus="focus"
-        @blur="blur"
+        v-model="state.val6"
+        step="0.1"
+        decimal-places="1"
+        readonly
       />
     </nut-cell>
     <h2>支持异步修改</h2>
     <nut-cell>
-      <nut-inputnumber
-        :async="state.async"
-        v-model="state.val5"
-        :before-change="beforeChange"
-      />
+      <nut-inputnumber :model-value="state.val8" @change="onChange" />
+    </nut-cell>
+    <h2>自定义按钮大小</h2>
+    <nut-cell>
+      <nut-inputnumber v-model="state.val7" button-size="30" input-width="50" />
     </nut-cell>
   </div>
 </template>
 
 <script lang="ts">
-import { reactive, onMounted } from 'vue';
+import { reactive, getCurrentInstance } from 'vue';
 import { createComponent } from '@/utils/create';
 const { createDemo } = createComponent('inputnumber');
 export default createDemo({
   props: {},
   setup() {
+    let { ctx } = getCurrentInstance();
+
     const state = reactive({
-      val1: 2,
-      val2: 1.1,
-      val3: 3,
+      val1: 1,
+      val2: 0,
+      val3: 10,
       val4: 0,
       val5: 1,
-      step: 1.1,
-      min: 3,
-      max: 100,
-      async: true,
-      timer: undefined as undefined | number
-    });
-    onMounted(() => {
-      state.max = 5;
+      val6: 5.5,
+      val7: 1,
+      val8: 1,
+      step: 1.1
     });
-    const change = (num: string | number) => {
-      console.log('change: ', num);
-    };
-    const blur = (e: Event, num: string | number) => {
-      console.log('blur: ', num);
-    };
-    const focus = (e: Event, num: string | number) => {
-      console.log('focus: ', e, num);
-    };
-    const addNoAllow = () => {
-      alert('超出最大限制数');
-    };
-    const reduceNoAllow = () => {
-      alert('超出最小限制数');
-    };
-    const beforeChange = () => {
-      // return true;
-      return new Promise(resolve => {
-        setTimeout(() => {
-          resolve(true);
-        }, 500);
-      });
+
+    const onChange = (value: number) => {
+      ctx.$toast.loading('异步演示 2 秒后更改');
+      setTimeout(() => {
+        state.val8 = value;
+        ctx.$toast.hide();
+      }, 2000);
     };
-    const handleChangeAsync = (num: number) => {
-      clearTimeout(state.timer);
-      state.timer = setTimeout(() => {
-        state.val5 = state.val5 + 1;
-      }, 1000);
+
+    const overlimit = () => {
+      ctx.$toast.warn('超出限制事件触发');
     };
+
     return {
       state,
-      change,
+      onChange,
       blur,
       focus,
-      reduceNoAllow,
-      addNoAllow,
-      handleChangeAsync,
-      beforeChange
+      overlimit
     };
   }
 });

+ 76 - 48
src/packages/inputnumber/doc.md

@@ -1,105 +1,133 @@
-# InputNumber 数字输入框组件
+# InputNumber 数字输入框
 
 ### 介绍
 
-由加、减按钮以及输入框组成,用于输入一定范围的数字
+通过点击按钮控制数字增减
 
 ### 安装
 
 ``` javascript
 import { createApp } from 'vue';
-import { inputnumber } from '@nutui/nutui';
+import { InputNumber,Icon } from '@nutui/nutui';
 
 const app = createApp();
-app.use(inputnumber);
+app.use(InputNumber).use(Icon);
 
 ```
 
 ## 代码演示
 
-### 基础用法1
+### 基础用法
 
 初始化一个默认值
 
-```html
-<nut-inputnumber v-model:modelValue="1" />
+``` html
+<nut-inputnumber v-model="value" />
+```
+``` javascript
+import { ref } from 'vue';
+
+export default {
+  setup() {
+    const value = ref(1);
+    return { value };
+  },
+};
 ```
 
-### 基础用法2
+### 步长设置
 
-设置步长`step` 和 保留的小数位`decimalPlaces`
+设置步长 `step` 5 
 
 ```html
-<nut-inputnumber v-model:modelValue="1" :step="1.1" :decimalPlaces="1" />
+<nut-inputnumber v-model="value" step="5" />
 ```
 
-### 基础用法3
+### 限制输入范围
 
 `min` 和 `max` 属性分别表示最小值和最大值
 
 ```html
-<nut-inputnumber v-model:modelValue="1" :min="3" :max="5" />
+<nut-inputnumber v-model="value" min="10" max="20" />
 ```
 
-### 基础用法4
+### 禁用状态
 
-`readonly`设置只读
+`disabled` 禁用状态下无法点击按钮或修改输入框。
 
 ```html
-<nut-inputnumber v-model:modelValue="1" :readonly="true" />
+<nut-inputnumber v-model="value" disabled />
 ```
 
-### 基础用法5
+### 只读禁用输入框
 
-`size`设置操作符的大小
+`readonly` 设置只读禁用输入框输入行为
 
 ```html
-<nut-inputnumber v-model:modelValue="1" :size="20px" />
+<nut-inputnumber v-model="value" readonly />
 ```
 
-### 高级用法
+### 支持小数点
+
+设置步长 `step` 0.1  `decimal-places` 小数保留1位
+
+```html
+<nut-inputnumber v-model="value" step="0.1" decimal-places="1" />
+```
+### 支持异步修改
 
-`before-change`支持异步修改数量
+通过 `change` 事件和 `model-value` 进行异步修改
 
 ```html
-<nut-inputnumber v-model:modelValue="1" :before-change="true"/>
+<nut-inputnumber :model-value="value" @change="onChange" />
 ```
 
-```js
-  const beforeChange = () => {
-    // return true;
-    return new Promise(resolve => {
+``` javascript
+import { ref } from 'vue';
+
+export default {
+  setup() {
+    const value = ref(1);
+    const onChange = (value: number) => {
       setTimeout(() => {
-        resolve(true);
-      }, 500);
-    });
-  };
+        value.value = value;
+      }, 2000);
+    };
+    return { value,onChange };
+  },
+};
 ```
+### 自定义按钮大小
 
+设置步长 `step` 0.1  `decimal-places` 小数保留1位
+
+```html
+<nut-inputnumber v-model="value"  button-size="30" input-width="50" />
+```
 
 ## API
 
 ### Props
 
-| 参数         | 说明                             | 类型   | 默认值           |
-|--------------|----------------------------------|--------|------------------|
-| size         | 操作符+、-尺寸               | String          | `20px`                |
-| color        | 操作符+、-颜色               | String          | `#1a1a1a `            |
-| dis-color     | 操作符+、-禁用时颜色          | String          | `#ccc`                |
-| min          | 最小值                      | String、Number | `1`                   |
-| max          | 最大值                      | String、Number | `Infinity`             |
-| step         | 步长                        | String、Number |     `1`                |
-| readonly     | 只读                   | Boolean | false              |
-| modelValue   | 初始值                   | String、Number | `''`              |
-| decimal-places| 设置保留的小数位                   | String、Number | `1`              |
-| before-change        | 支持异步                   | Function | -              |
+| 参数           | 说明                       | 类型           | 默认值     |
+|----------------|----------------------------|----------------|------------|
+| v-model        | 初始值                     | String、Number | -          |
+| input-width    | 输入框宽度                 | String         | `40px`     |
+| button-size    | 操作符+、-尺寸             | String         | `20px`     |
+| min            | 最小值限制                 | String、Number | `1`        |
+| max            | 最大值限制                 | String、Number | `Infinity` |
+| step           | 步长                       | String、Number | `1`        |
+| decimal-places | 设置保留的小数位           | String、Number | `0`        |
+| disabled       | 禁用所有功能               | Boolean        | false      |
+| readonly       | 只读状态禁用输入框操作行为 | Boolean        | false      |
 
 ### Events
 
-| 事件名 | 说明           | 回调参数     |
-|--------|----------------|--------------|
-| change  | 值改变时触发 | num: string | number |
-| focus  | 输入框获取焦点时触发 | event: Event, num: string | number |
-| blur  | 输入框失去焦点时触发 | event: Event, num: string | number |
-| add-no-allow  | 超出最大事件回调 | - |
-| reduce-no-allow  | 超出最小事件回调 | - |
+| 事件名    | 说明                   | 回调参数                       |
+|-----------|------------------------|--------------------------------|
+| add       | 点击增加按钮时触发     | event: Event                   |
+| reduce    | 点击减少按钮时触发     | event: Event                   |
+| overlimit | 点击不可用的按钮时触发 | event: Event                   |
+| change    | 值改变时触发           | value:  number , event : Event |
+| blur      | 输入框失去焦点时触发   | event: Event                   |
+| focus     | 输入框获得焦点时触发   | event: Event                   |

+ 19 - 5
src/packages/inputnumber/index.scss

@@ -1,18 +1,32 @@
 .nut-inputnumber {
   display: flex;
   align-items: center;
+
+  &--disabled {
+    input {
+      color: $inputnumber-icon-void-color;
+    }
+  }
+
+  &__icon {
+    color: $inputnumber-icon-color;
+    font-size: $inputnumber-icon-size;
+    cursor: pointer;
+    &--disabled {
+      color: $inputnumber-icon-void-color;
+      cursor: not-allowed;
+    }
+  }
+
   input {
     width: $inputnumber-input-width;
-    height: $inputnumber-input-height;
+    height: 100%;
     text-align: center;
     outline: none;
     border: 0;
-    font-family: initial;
+    margin: 0 6px;
     background-color: $inputnumber-input-background-color;
     border-radius: $inputnumber-input-border-radius;
-    &:read-only {
-      color: $disable-color;
-    }
   }
   input::-webkit-outer-spin-button,
   input::-webkit-inner-spin-button {

+ 82 - 255
src/packages/inputnumber/index.vue

@@ -2,59 +2,56 @@
   <view :class="classes">
     <nut-icon
       name="minus"
-      :size="size"
-      :color="getIconColor('minus')"
+      class="nut-inputnumber__icon"
+      :class="{ 'nut-inputnumber__icon--disabled': !reduceAllow() }"
+      :size="buttonSize"
       @click="reduce"
-    ></nut-icon>
+    >
+    </nut-icon>
     <input
       type="number"
-      :min="state.minVal"
+      :min="min"
       :max="max"
+      :style="{ width: pxCheck(inputWidth) }"
+      :disabled="disabled"
       :readonly="readonly"
-      :value="state.num"
-      @input="numChange"
+      :value="modelValue"
+      @input="change"
       @blur="blur"
       @focus="focus"
     />
     <nut-icon
       name="plus"
-      :size="size"
-      :color="getIconColor('plus')"
+      class="nut-inputnumber__icon"
+      :class="{ 'nut-inputnumber__icon--disabled': !addAllow() }"
+      :size="buttonSize"
       @click="add"
-    ></nut-icon>
+    >
+    </nut-icon>
   </view>
 </template>
 <script lang="ts">
-import { computed, reactive, watch, toRefs } from 'vue';
+import { computed } from 'vue';
 import { createComponent } from '@/utils/create';
+import { pxCheck } from '@/utils/pxCheck';
 const { componentName, create } = createComponent('inputnumber');
-interface Events {
-  eventName:
-    | 'update:modelValue'
-    | 'change'
-    | 'focus'
-    | 'blur'
-    | 'add-no-allow'
-    | 'reduce-no-allow';
-  params: (string | number | Event)[];
-}
 export default create({
   props: {
-    size: {
-      type: [String],
-      default: '20px'
+    modelValue: {
+      type: [Number, String],
+      default: 0
     },
-    color: {
-      type: String,
-      default: '#1a1a1a'
+    inputWidth: {
+      type: [Number, String],
+      default: ''
     },
-    disColor: {
-      type: String,
-      default: '#ccc'
+    buttonSize: {
+      type: [Number, String],
+      default: ''
     },
     min: {
       type: [Number, String],
-      default: 0
+      default: 1
     },
     max: {
       type: [Number, String],
@@ -64,20 +61,17 @@ export default create({
       type: [Number, String],
       default: 1
     },
-    readonly: {
-      type: Boolean,
-      default: false
-    },
-    modelValue: {
-      type: [String, Number],
-      default: ''
-    },
     decimalPlaces: {
-      type: [String, Number],
+      type: [Number, String],
       default: 0
     },
-    beforeChange: {
-      type: Function
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    readonly: {
+      type: Boolean,
+      default: false
     }
   },
   emits: [
@@ -86,260 +80,93 @@ export default create({
     'blur',
     'focus',
     'reduce',
-    'reduce-no-allow',
     'add',
-    'add-no-allow'
+    'overlimit'
   ],
 
   setup(props, { emit }) {
-    const { modelValue, min, max, step } = toRefs(props);
-    const state = reactive({
-      num: !modelValue.value ? min.value : modelValue.value,
-      minVal: min.value,
-      tempVal: '',
-      focusing: false
-    });
-
     const classes = computed(() => {
       const prefixCls = componentName;
       return {
-        [prefixCls]: true
+        [prefixCls]: true,
+        [`${prefixCls}--disabled`]: props.disabled
       };
     });
 
-    const isPromise = (obj: any) => {
-      return (
-        !!obj &&
-        (typeof obj === 'object' || typeof obj === 'function') &&
-        typeof obj.then === 'function'
-      );
-    };
-
-    const callInterceptor = (
-      interceptor: Function,
-      done: Function,
-      fail?: Function
-    ) => {
-      const res = interceptor.apply(null, arguments || []);
-      if (interceptor) {
-        if (isPromise(res)) {
-          res.then((value: boolean) => {
-            if (value) {
-              done();
-            } else {
-              fail && fail();
-            }
-          });
-        } else if (res) {
-          done();
-        } else if (fail) {
-          fail();
-        }
-      } else {
-        done();
-      }
-    };
-
-    const format = (v: string | number): string | number => {
-      if (v > max.value) {
-        v = max.value;
-      }
-      if (v < state.minVal) {
-        v = state.minVal;
-      }
-
-      return v;
-    };
-
     const fixedDecimalPlaces = (v: string | number): string => {
       return Number(v).toFixed(Number(props.decimalPlaces));
     };
 
-    const getIconColor = (type: 'minus' | 'plus') => {
-      if (type === 'minus') {
-        return (state.focusing ? Number(state.tempVal) : Number(state.num)) -
-          Number(step.value) <
-          min.value
-          ? props.disColor
-          : props.color;
-      } else if (type === 'plus') {
-        return Number(state.num) > Number(max.value) - Number(step.value)
-          ? props.disColor
-          : props.color;
-      } else {
-        throw new Error('type is not be supported~');
-      }
+    const change = (event: Event) => {
+      const input = event.target as HTMLInputElement;
+      emit('update:modelValue', input.valueAsNumber, event);
     };
 
-    const emitChange = (envs: Events[]) => {
-      envs.forEach(item => {
-        emit(item.eventName, ...item.params);
-      });
+    const emitChange = (value: string | number, event: Event) => {
+      let outup_value: number = Number(fixedDecimalPlaces(value));
+      emit('change', outup_value, event);
+      emit('update:modelValue', outup_value, event);
     };
 
-    const numChange = (e: Event) => {
-      const input = e.target as HTMLInputElement;
-      let val = input.value;
-      val = String(format(val));
-      input.value = val;
-      if (props.beforeChange) {
-        callInterceptor(props.beforeChange, () => {
-          state.num = val;
-        });
-      } else {
-        state.num = val;
-      }
-
-      emitChange([
-        {
-          eventName: 'update:modelValue',
-          params: [state.num]
-        },
-        {
-          eventName: 'change',
-          params: [state.num]
-        }
-      ]);
+    const addAllow = (value = Number(props.modelValue)): boolean => {
+      return value < Number(props.max) && !props.disabled;
     };
 
-    const focus = (e: Event) => {
-      if (props.readonly) return;
-      const val = String(state.num);
-      state.tempVal = val;
-      state.minVal = '';
-      state.focusing = true;
-      emitChange([
-        {
-          eventName: 'focus',
-          params: [e, state.num]
-        }
-      ]);
+    const reduceAllow = (value = Number(props.modelValue)): boolean => {
+      return value > Number(props.min) && !props.disabled;
     };
 
-    const blur = (e: Event) => {
-      const val = (e.target as HTMLInputElement).value;
-      state.minVal = String(min.value);
-      if (props.beforeChange) {
-        callInterceptor(props.beforeChange, () => {
-          state.num = val ? format(val) : state.tempVal;
-        });
+    const reduce = (event: Event) => {
+      emit('reduce', event);
+      if (reduceAllow()) {
+        let outup_value = Number(props.modelValue) - Number(props.step);
+        emitChange(outup_value, event);
       } else {
-        state.num = val ? format(val) : state.tempVal;
+        emit('overlimit', event);
       }
-      state.focusing = false;
-      emitChange([
-        {
-          eventName: 'update:modelValue',
-          params: [state.num]
-        },
-        {
-          eventName: 'blur',
-          params: [e, state.num]
-        }
-      ]);
     };
 
-    const reduce = () => {
-      if (getIconColor('minus') === props.color) {
-        const [n1, n2] = fixedDecimalPlaces(
-          Number(state.num) - Number(props.step)
-        ).split('.');
-        const fixedLen = n2 ? n2.length : 0;
-        if (props.beforeChange) {
-          callInterceptor(props.beforeChange, () => {
-            state.num = parseFloat(n1 + (n2 ? `.${n2}` : '')).toFixed(fixedLen);
-          });
-        } else {
-          state.num = parseFloat(n1 + (n2 ? `.${n2}` : '')).toFixed(fixedLen);
-        }
-
-        emitChange([
-          {
-            eventName: 'update:modelValue',
-            params: [state.num]
-          },
-          {
-            eventName: 'change',
-            params: [state.num]
-          }
-        ]);
+    const add = (event: Event) => {
+      emit('add', event);
+      if (addAllow()) {
+        let outup_value = Number(props.modelValue) + Number(props.step);
+        emitChange(outup_value, event);
       } else {
-        emitChange([
-          {
-            eventName: 'reduce-no-allow',
-            params: []
-          }
-        ]);
+        emit('overlimit', event);
       }
     };
 
-    const add = () => {
-      if (getIconColor('plus') === props.color) {
-        const [n1, n2] = fixedDecimalPlaces(
-          Number(state.num) + Number(props.step)
-        ).split('.');
-        const fixedLen = n2 ? n2.length : 0;
-        if (props.beforeChange) {
-          callInterceptor(props.beforeChange, () => {
-            state.num = parseFloat(n1 + (n2 ? '.' + n2 : '')).toFixed(fixedLen);
-          });
-        } else {
-          state.num = parseFloat(n1 + (n2 ? '.' + n2 : '')).toFixed(fixedLen);
-        }
-        emitChange([
-          {
-            eventName: 'update:modelValue',
-            params: [state.num]
-          },
-          {
-            eventName: 'change',
-            params: [state.num]
-          }
-        ]);
-      } else {
-        emitChange([
-          {
-            eventName: 'add-no-allow',
-            params: []
-          }
-        ]);
-      }
+    const focus = (event: Event) => {
+      if (props.disabled) return;
+      if (props.readonly) return;
+      emit('focus', event);
     };
 
-    watch(
-      () => min.value,
-      newValues => {
-        if (newValues < Number(max.value)) {
-          state.minVal = newValues;
-          const val = format(state.num);
-          state.num = val > 0 ? fixedDecimalPlaces(val) : val;
-        }
+    const blur = (event: Event) => {
+      if (props.disabled) return;
+      if (props.readonly) return;
+      const input = event.target as HTMLInputElement;
+      let value = input.valueAsNumber;
+      if (!addAllow(value)) {
+        value = Number(props.max);
       }
-    );
-
-    watch(
-      () => modelValue.value,
-      newValues => {
-        const val = format(newValues);
-        state.num = val > 0 ? fixedDecimalPlaces(val) : val;
-        emitChange([
-          {
-            eventName: 'change',
-            params: [state.num]
-          }
-        ]);
+      if (!reduceAllow(value)) {
+        value = Number(props.max);
       }
-    );
+      emitChange(value, event);
+      emit('blur', event);
+    };
 
     return {
-      state,
       classes,
-      getIconColor,
-      numChange,
+      change,
       blur,
       focus,
+      add,
+      addAllow,
       reduce,
-      add
+      reduceAllow,
+      pxCheck
     };
   }
 });

+ 4 - 2
src/styles/variables.scss

@@ -115,10 +115,12 @@ $input-disabled-color: #c8c9cc;
 
 // inputnumber
 
+$inputnumber-icon-color: $title-color;
+$inputnumber-icon-void-color: $disable-color;
 $inputnumber-input-background-color: $help-color;
-$inputnumber-input-border-radius: 8px;
+$inputnumber-input-border-radius: 4px;
 $inputnumber-input-width: 40px;
-$inputnumber-input-height: 20px;
+$inputnumber-icon-size: 20px;
 
 // actionsheet
 $zindex-actionsheet: 10001 !default;