index.taro.vue 11.3 KB
Newer Older
G
guoxiaoxiao8 已提交
1
<template>
A
ailululu 已提交
2
  <view :class="classes">
3
    <view v-if="leftIcon && leftIcon.length > 0" class="nut-input-left-icon" @click="onClickLeftIcon">
4
      <nut-icon :name="leftIcon" :size="leftIconSize"></nut-icon>
5 6 7 8 9 10 11 12 13 14 15 16 17
    </view>
    <view
      v-if="label"
      class="nut-input-label"
      :class="labelClass"
      :style="{
        width: `${labelWidth}px`,
        textAlign: labelAlign
      }"
    >
      <view class="label-string">
        {{ label }}
        {{ colon ? ':' : '' }}
A
ailululu 已提交
18
      </view>
19 20
    </view>
    <template v-if="$slots.input">
21 22 23
      <view class="nut-input-value">
        <view class="nut-input-inner" @click="onClickInput">
          <slot name="input"></slot>
A
ailululu 已提交
24 25
        </view>
      </view>
26 27 28 29
    </template>
    <template v-else>
      <view class="nut-input-value">
        <view class="nut-input-inner">
30
          <view class="nut-input-box">
31 32 33
            <textarea
              v-if="type == 'textarea'"
              class="input-text"
34
              v-bind="$attrs"
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
              ref="inputRef"
              :style="stylesTextarea"
              :maxlength="maxLength"
              :placeholder="placeholder"
              placeholder-class="nut-placeholder"
              :disabled="disabled"
              :readonly="readonly"
              :value="modelValue"
              :formatTrigger="formatTrigger"
              :adjust-position="adjustPosition"
              :always-system="alwaysSystem"
              @input="onInput"
              @focus="onFocus"
              @blur="onBlur"
              @click="onClickInput"
            />
            <input
              v-else
53
              class="input-text"
54
              v-bind="$attrs"
55 56
              ref="inputRef"
              :style="styles"
57
              :type="inputType(type)"
58
              :maxlength="maxLength"
A
ailululu 已提交
59
              :placeholder="placeholder"
60
              placeholder-class="nut-placeholder"
61 62 63 64
              :disabled="disabled"
              :readonly="readonly"
              :value="modelValue"
              :formatTrigger="formatTrigger"
65 66 67
              :confirm-type="confirmType"
              :adjust-position="adjustPosition"
              :always-system="alwaysSystem"
68 69
              :autofocus="autofocus"
              :enterkeyhint="confirmType"
70 71 72 73
              @input="onInput"
              @focus="onFocus"
              @blur="onBlur"
              @click="onClickInput"
74
            />
75 76 77
            <view v-if="readonly" class="nut-input-disabled-mask" @click="onClickInput"></view>
          </view>
          <view class="nut-input-clear-box">
78 79 80 81 82 83 84 85 86
            <nut-icon
              class="nut-input-clear"
              v-if="clearable && !readonly"
              v-show="active && modelValue.length > 0"
              :name="clearIcon"
              :size="clearSize"
              @click="clear"
            >
            </nut-icon>
87
          </view>
88
          <view v-if="rightIcon && rightIcon.length > 0" class="nut-input-right-icon" @click="onClickRightIcon">
89
            <nut-icon :name="rightIcon" :size="rightIconSize"></nut-icon>
90 91
          </view>
          <slot v-if="$slots.button" name="button" class="nut-input-button"></slot>
A
ailululu 已提交
92
          <slot v-if="$slots.rightExtra" name="rightExtra"></slot>
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
        </view>
        <view v-if="showWordLimit && maxLength" class="nut-input-word-limit">
          <span class="nut-input-word-num">{{ modelValue ? modelValue.length : 0 }}</span
          >/{{ maxLength }}
        </view>
        <view
          v-if="errorMessage"
          class="nut-input-error-message"
          :style="{
            textAlign: errorMessageAlign
          }"
        >
          {{ errorMessage }}
        </view>
      </view>
    </template>
G
guoxiaoxiao8 已提交
109 110 111
  </view>
</template>
<script lang="ts">
112
import { PropType, ref, reactive, computed, onMounted, watch, ComputedRef, InputHTMLAttributes } from 'vue';
113
import { createComponent } from '@/packages/utils/create';
A
ailululu 已提交
114
import { formatNumber } from './util';
G
guoxiaoxiao8 已提交
115

A
ailululu 已提交
116
const { componentName, create } = createComponent('input');
A
ailululu 已提交
117

118 119 120 121 122 123 124 125 126 127
export type InputType = InputHTMLAttributes['type'];
export type InputAlignType = 'left' | 'center' | 'right'; // text-align
export type InputFormatTrigger = 'onChange' | 'onBlur'; // onChange: 在输入时执行格式化 ; onBlur: 在失焦时执行格式化
export type InputRule = {
  pattern?: RegExp;
  message?: string;
  required?: boolean;
};
export type ConfirmTextType = 'send' | 'search' | 'next' | 'go' | 'done';

G
guoxiaoxiao8 已提交
128
export default create({
129
  inheritAttrs: false,
G
guoxiaoxiao8 已提交
130
  props: {
A
ailululu 已提交
131
    ref: {
G
guoxiaoxiao8 已提交
132
      type: String,
A
ailululu 已提交
133 134 135
      default: ''
    },
    type: {
136
      type: String as PropType<InputType>,
G
guoxiaoxiao8 已提交
137 138 139
      default: 'text'
    },
    modelValue: {
140
      type: String,
G
guoxiaoxiao8 已提交
141 142 143 144
      default: ''
    },
    placeholder: {
      type: String,
145
      default: ''
G
guoxiaoxiao8 已提交
146 147 148 149 150
    },
    label: {
      type: String,
      default: ''
    },
A
ailululu 已提交
151 152 153 154 155 156 157 158 159
    labelClass: {
      type: String,
      default: ''
    },
    labelWidth: {
      type: [String, Number],
      default: '80'
    },
    labelAlign: {
160
      type: String as PropType<InputAlignType>,
A
ailululu 已提交
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      default: 'left'
    },
    colon: {
      type: Boolean,
      default: false
    },
    inputAlign: {
      type: String,
      default: 'left'
    },
    center: {
      type: Boolean,
      default: false
    },
    required: {
G
guoxiaoxiao8 已提交
176 177 178 179 180 181 182 183 184 185 186
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    },
    readonly: {
      type: Boolean,
      default: false
    },
A
ailululu 已提交
187 188 189 190
    error: {
      type: Boolean,
      default: false
    },
191
    maxLength: {
A
ailululu 已提交
192
      type: [String, Number],
193
      default: '9999'
A
ailululu 已提交
194 195
    },
    leftIconSize: {
G
guoxiaoxiao8 已提交
196
      type: [String, Number],
A
ailululu 已提交
197 198
      default: ''
    },
199 200 201 202
    leftIcon: {
      type: String,
      default: ''
    },
A
ailululu 已提交
203 204 205 206 207 208 209
    rightIcon: {
      type: String,
      default: ''
    },
    rightIconSize: {
      type: [String, Number],
      default: ''
G
guoxiaoxiao8 已提交
210 211
    },
    clearable: {
A
ailululu 已提交
212 213 214 215 216 217 218 219 220 221 222 223
      type: Boolean,
      default: false
    },
    clearIcon: {
      type: String,
      default: 'mask-close'
    },
    clearSize: {
      type: [String, Number],
      default: '14'
    },
    border: {
G
guoxiaoxiao8 已提交
224 225
      type: Boolean,
      default: true
226
    },
A
ailululu 已提交
227
    formatTrigger: {
228
      type: String as PropType<InputFormatTrigger>,
A
ailululu 已提交
229 230 231 232 233 234 235
      default: 'onChange'
    },
    formatter: {
      type: Function as PropType<(value: string) => string>,
      default: null
    },
    rules: {
236
      type: Array as PropType<InputRule>,
A
ailululu 已提交
237 238 239 240 241 242 243
      default: []
    },
    errorMessage: {
      type: String,
      default: ''
    },
    errorMessageAlign: {
244
      type: String as PropType<InputAlignType>,
A
ailululu 已提交
245 246 247 248 249 250 251
      default: ''
    },
    rows: {
      type: [String, Number],
      default: null
    },
    showWordLimit: {
252
      type: Boolean,
253
      default: false
A
ailululu 已提交
254 255 256 257
    },
    autofocus: {
      type: Boolean,
      default: false
258
    },
259
    confirmType: {
260
      type: String as PropType<ConfirmTextType>,
261
      default: 'done'
262
    },
263 264 265
    adjustPosition: {
      type: Boolean,
      default: true
266 267 268 269
    },
    alwaysSystem: {
      type: Boolean,
      default: false
G
guoxiaoxiao8 已提交
270 271 272
    }
  },

A
ailululu 已提交
273 274 275 276 277 278 279 280 281 282 283
  emits: [
    'update:modelValue',
    'change',
    'blur',
    'focus',
    'clear',
    'keypress',
    'click-input',
    'click-left-icon',
    'click-right-icon'
  ],
G
guoxiaoxiao8 已提交
284

A
ailululu 已提交
285
  setup(props, { emit, slots }) {
G
guoxiaoxiao8 已提交
286 287
    const active = ref(false);

288
    const inputRef = ref();
A
ailululu 已提交
289
    const getModelValue = () => String(props.modelValue ?? '');
290

A
ailululu 已提交
291 292 293 294 295 296
    const state = reactive({
      focused: false,
      validateFailed: false, // 校验失败
      validateMessage: '' // 校验信息
    });

G
guoxiaoxiao8 已提交
297 298 299 300
    const classes = computed(() => {
      const prefixCls = componentName;
      return {
        [prefixCls]: true,
A
ailululu 已提交
301
        center: props.center,
D
dongj0316 已提交
302
        [`${prefixCls}-readonly`]: props.readonly,
303
        [`${prefixCls}-disabled`]: props.disabled,
A
ailululu 已提交
304 305 306
        [`${prefixCls}-required`]: props.required,
        [`${prefixCls}-error`]: props.error,
        [`${prefixCls}-border`]: props.border
G
guoxiaoxiao8 已提交
307 308 309
      };
    });

310
    const styles: ComputedRef = computed(() => {
G
guoxiaoxiao8 已提交
311
      return {
A
ailululu 已提交
312 313 314
        textAlign: props.inputAlign
      };
    });
315
    const stylesTextarea: ComputedRef = computed(() => {
A
ailululu 已提交
316 317 318
      return {
        textAlign: props.inputAlign,
        height: Number(props.rows) * 24 + 'px'
G
guoxiaoxiao8 已提交
319 320 321
      };
    });

322
    const inputType = (type: InputType) => {
A
ailululu 已提交
323 324 325 326 327 328 329 330
      if (type === 'number') {
        return 'text';
      } else if (type === 'digit') {
        return 'tel';
      } else {
        return type;
      }
    };
G
guoxiaoxiao8 已提交
331

A
ailululu 已提交
332 333 334
    const onInput = (event: Event) => {
      const input = event.target as HTMLInputElement;
      let value = input.value;
335 336 337
      if (props.maxLength && value.length > Number(props.maxLength)) {
        value = value.slice(0, Number(props.maxLength));
      }
A
ailululu 已提交
338 339 340
      updateValue(value);
    };

341
    const updateValue = (value: string, trigger: InputFormatTrigger = 'onChange') => {
G
guoxiaoxiao8 已提交
342
      if (props.type === 'digit') {
A
ailululu 已提交
343
        value = formatNumber(value, false, false);
G
guoxiaoxiao8 已提交
344 345
      }
      if (props.type === 'number') {
A
ailululu 已提交
346 347 348 349 350 351 352
        value = formatNumber(value, true, true);
      }

      if (props.formatter && trigger === props.formatTrigger) {
        value = props.formatter(value);
      }

353
      if (inputRef?.value !== value) {
354
        inputRef.value = value;
355 356
      }

A
ailululu 已提交
357 358 359
      if (value !== props.modelValue) {
        emit('update:modelValue', value);
        emit('change', value);
G
guoxiaoxiao8 已提交
360 361 362
      }
    };

A
ailululu 已提交
363
    const onFocus = (event: Event) => {
364 365 366
      if (props.disabled || props.readonly) {
        return;
      }
G
guoxiaoxiao8 已提交
367 368 369
      const input = event.target as HTMLInputElement;
      let value = input.value;
      active.value = true;
370 371
      emit('focus', event);
      emit('update:modelValue', value);
G
guoxiaoxiao8 已提交
372 373
    };

A
ailululu 已提交
374
    const onBlur = (event: Event) => {
375 376 377
      if (props.disabled || props.readonly) {
        return;
      }
G
guoxiaoxiao8 已提交
378 379
      setTimeout(() => {
        active.value = false;
A
ailululu 已提交
380 381
      }, 200);

G
guoxiaoxiao8 已提交
382 383
      const input = event.target as HTMLInputElement;
      let value = input.value;
384 385
      if (props.maxLength && value.length > Number(props.maxLength)) {
        value = value.slice(0, Number(props.maxLength));
A
ailululu 已提交
386 387
      }
      updateValue(getModelValue(), 'onBlur');
388 389
      emit('blur', event);
      emit('update:modelValue', value);
G
guoxiaoxiao8 已提交
390 391
    };

A
ailululu 已提交
392
    const clear = (event: Event) => {
393
      event.stopPropagation();
394
      if (props.disabled) return;
A
ailululu 已提交
395 396 397 398 399 400 401 402 403 404 405
      emit('clear', '', event);
    };

    const resetValidation = () => {
      if (state.validateFailed) {
        state.validateFailed = false;
        state.validateMessage = '';
      }
    };

    const onClickInput = (event: MouseEvent) => {
406 407 408
      if (props.disabled) {
        return;
      }
A
ailululu 已提交
409
      emit('click-input', event);
G
guoxiaoxiao8 已提交
410 411
    };

412
    const onClickLeftIcon = (event: MouseEvent) => {
413
      event.stopPropagation();
414 415 416 417 418 419 420
      if (props.disabled) {
        return;
      }
      emit('click-left-icon', event);
    };

    const onClickRightIcon = (event: MouseEvent) => {
421
      event.stopPropagation();
422 423 424 425 426
      if (props.disabled) {
        return;
      }
      emit('click-right-icon', event);
    };
A
ailululu 已提交
427 428 429 430

    watch(
      () => props.modelValue,
      () => {
431 432 433 434
        if (!slots.input) {
          updateValue(getModelValue());
          resetValidation();
        }
A
ailululu 已提交
435 436 437 438
      }
    );

    onMounted(() => {
439 440 441 442 443
      if (!slots.input) {
        if (props.autofocus) {
          inputRef.value.focus();
        }
        updateValue(getModelValue(), props.formatTrigger);
444
      }
A
ailululu 已提交
445 446
    });

G
guoxiaoxiao8 已提交
447
    return {
A
ailululu 已提交
448
      inputRef,
G
guoxiaoxiao8 已提交
449 450 451
      active,
      classes,
      styles,
A
ailululu 已提交
452 453 454 455 456 457 458 459
      stylesTextarea,
      inputType,
      onInput,
      onFocus,
      onBlur,
      clear,
      onClickInput,
      onClickLeftIcon,
A
ailululu 已提交
460
      onClickRightIcon
G
guoxiaoxiao8 已提交
461 462 463 464
    };
  }
});
</script>