| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- <template>
- <view :class="classes">
- <view class="nut-input-value">
- <view class="nut-input-inner">
- <view class="nut-input-left-box">
- <slot name="left"></slot>
- </view>
- <view class="nut-input-box">
- <component
- :is="renderInput(type)"
- v-bind="$attrs"
- class="input-text"
- ref="inputRef"
- :style="styles"
- :maxlength="maxLength ? maxLength : undefined"
- :placeholder="placeholder"
- :disabled="disabled"
- :readonly="readonly"
- :value="modelValue"
- :formatTrigger="formatTrigger"
- :autofocus="autofocus ? true : undefined"
- :enterkeyhint="confirmType"
- @input="onInput"
- @focus="onFocus"
- @blur="onBlur"
- @click="onClickInput"
- @change="endComposing"
- @compositionend="endComposing"
- @compositionstart="startComposing"
- :adjust-position="adjustPosition"
- :always-system="alwaysSystem"
- ></component>
- <view v-if="readonly" class="nut-input-disabled-mask" @click="onClickInput"></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>
- <view
- class="nut-input-clear-box"
- v-if="clearable && !readonly"
- v-show="(active || showClearIcon) && modelValue.length > 0"
- >
- <slot name="clear">
- <MaskClose class="nut-input-clear" :size="clearSize" :width="clearSize" :height="clearSize" @click="clear">
- </MaskClose>
- </slot>
- </view>
- <view class="nut-input-right-box">
- <slot name="right"> </slot>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script lang="ts">
- import { PropType, ref, reactive, computed, onMounted, watch, ComputedRef, InputHTMLAttributes, h } from 'vue';
- import { createComponent } from '@/packages/utils/create';
- import { formatNumber } from './util';
- import { MaskClose } from '@nutui/icons-vue-taro';
- import Taro from '@tarojs/taro';
- const { componentName, create } = createComponent('input');
- 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';
- export interface InputTarget extends HTMLInputElement {
- composing?: boolean;
- }
- export default create({
- inheritAttrs: false,
- props: {
- type: {
- type: String as PropType<InputType>,
- default: 'text'
- },
- modelValue: {
- type: String,
- default: ''
- },
- placeholder: {
- type: String,
- default: ''
- },
- inputAlign: {
- type: String,
- default: 'left'
- },
- required: {
- type: Boolean,
- default: false
- },
- disabled: {
- type: Boolean,
- default: false
- },
- readonly: {
- type: Boolean,
- default: false
- },
- error: {
- type: Boolean,
- default: false
- },
- maxLength: {
- type: [String, Number],
- default: ''
- },
- clearable: {
- type: Boolean,
- default: false
- },
- clearSize: {
- type: [String, Number],
- default: '14'
- },
- border: {
- type: Boolean,
- default: true
- },
- formatTrigger: {
- type: String as PropType<InputFormatTrigger>,
- default: 'onChange'
- },
- formatter: {
- type: Function as PropType<(value: string) => string>,
- default: null
- },
- showWordLimit: {
- type: Boolean,
- default: false
- },
- autofocus: {
- type: Boolean,
- default: false
- },
- confirmType: {
- type: String as PropType<ConfirmTextType>,
- default: 'done'
- },
- adjustPosition: {
- type: Boolean,
- default: true
- },
- alwaysSystem: {
- type: Boolean,
- default: false
- },
- showClearIcon: {
- type: Boolean,
- default: false
- }
- },
- components: { MaskClose },
- emits: ['update:modelValue', 'blur', 'focus', 'clear', 'keypress', 'click-input'],
- setup(props, { emit, slots }) {
- const active = ref(false);
- const inputRef = ref();
- const getModelValue = () => String(props.modelValue ?? '');
- const renderInput = (type: InputType) => {
- return h('input', {
- style: styles,
- type: type != 'textarea' && inputType(type)
- });
- };
- const state = reactive({
- focused: false,
- validateFailed: false, // 校验失败
- validateMessage: '' // 校验信息
- });
- const classes = computed(() => {
- const prefixCls = componentName;
- return {
- [prefixCls]: true,
- [`${prefixCls}--disabled`]: props.disabled,
- [`${prefixCls}--required`]: props.required,
- [`${prefixCls}--error`]: props.error,
- [`${prefixCls}--border`]: props.border
- };
- });
- const styles: ComputedRef = computed(() => {
- return {
- textAlign: props.inputAlign
- };
- });
- const inputType = (type: InputType) => {
- if (type === 'number') {
- return 'text';
- } else if (type === 'digit') {
- return 'tel';
- } else {
- return type;
- }
- };
- const onInput = (event: Event) => {
- if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
- if (!(event.target as InputTarget)!.composing) {
- _onInput(event);
- }
- } else {
- _onInput(event);
- }
- };
- const _onInput = (event: Event) => {
- const input = event.target as HTMLInputElement;
- let value = input.value;
- if (props.maxLength && value.length > Number(props.maxLength)) {
- value = value.slice(0, Number(props.maxLength));
- }
- updateValue(value);
- };
- const updateValue = (value: string, trigger: InputFormatTrigger = 'onChange') => {
- if (props.type === 'digit') {
- value = formatNumber(value, false, false);
- }
- if (props.type === 'number') {
- value = formatNumber(value, true, true);
- }
- if (props.formatter && trigger === props.formatTrigger) {
- value = props.formatter(value);
- }
- if (inputRef?.value !== value) {
- inputRef.value = value;
- }
- if (value !== props.modelValue) {
- emit('update:modelValue', value);
- // emit('change', value);
- }
- };
- const onFocus = (event: Event) => {
- if (props.disabled || props.readonly) {
- return;
- }
- const input = event.target as HTMLInputElement;
- let value = input.value;
- active.value = true;
- emit('focus', event);
- // emit('update:modelValue', value);
- };
- const onBlur = (event: Event) => {
- if (props.disabled || props.readonly) {
- return;
- }
- setTimeout(() => {
- active.value = false;
- }, 200);
- const input = event.target as HTMLInputElement;
- let value = input.value;
- if (props.maxLength && value.length > Number(props.maxLength)) {
- value = value.slice(0, Number(props.maxLength));
- }
- updateValue(getModelValue(), 'onBlur');
- emit('blur', event);
- // emit('update:modelValue', value);
- };
- const clear = (event: Event) => {
- event.stopPropagation();
- if (props.disabled) return;
- emit('update:modelValue', '', event);
- // emit('change', '', event);
- emit('clear', '', event);
- };
- const resetValidation = () => {
- if (state.validateFailed) {
- state.validateFailed = false;
- state.validateMessage = '';
- }
- };
- const onClickInput = (event: MouseEvent) => {
- if (props.disabled) {
- return;
- }
- emit('click-input', event);
- };
- const startComposing = ({ target }: Event) => {
- if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
- (target as InputTarget)!.composing = true;
- }
- };
- const endComposing = ({ target }: Event) => {
- if (Taro.getEnv() === Taro.ENV_TYPE.WEB) {
- if ((target as InputTarget)!.composing) {
- (target as InputTarget)!.composing = false;
- (target as InputTarget)!.dispatchEvent(new Event('input'));
- }
- }
- };
- watch(
- () => props.modelValue,
- () => {
- updateValue(getModelValue());
- resetValidation();
- }
- );
- onMounted(() => {
- if (props.autofocus) {
- inputRef.value.focus();
- }
- updateValue(getModelValue(), props.formatTrigger);
- });
- return {
- renderInput,
- inputRef,
- active,
- classes,
- styles,
- inputType,
- onInput,
- onFocus,
- onBlur,
- clear,
- startComposing,
- endComposing,
- onClickInput
- };
- }
- });
- </script>
|