Vue3输入框(Input)

APIs

参数 说明 类型 默认值 必传
width 输入框宽度 string | number ‘100%’ false
addonBefore 设置前置标签 string | slot ‘’ false
addonAfter 设置后置标签 string | slot ‘’ false
allowClear 可以点击清除图标删除内容 boolean false false
password 是否启用密码框 boolean false false
disabled 是否禁用 boolean false false
maxlength 最大长度 number undefined false
showCount 是否展示字数 boolean false false
size 输入框大小 ‘large’ | ‘middle’ | ‘small’ ‘middle’ false
prefix 前缀图标 string ‘’ false
suffix 后缀图标 string ‘’ false
value(v-model) 输入框内容 string ‘’ false

Events

事件名称 说明 参数
change 输入框内容变化时的回调 (e: Event) => void
enter 按下回车的回调 (e: Event) => void

效果如下图:在线预览

Vue3输入框(Input)_第1张图片
Vue3输入框(Input)_第2张图片

创建输入框组件Input.vue

<script lang="ts">
/*
  一个根节点时,禁用组件根节点自动继承 attribute,必须使用这种写法!然后在要继承 attribute 的节点上绑定 v-bind="$attrs" 即可
  多个根节点时,只需在要继承 attribute 的节点上绑定 v-bind="$attrs" 即可
*/
export default {
  inheritAttrs: false
}
</script>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
interface Props {
  width?: string|number // 输入框宽度
  addonBefore?: string // 设置前置标签 string | slot
  addonAfter?: string // 设置后置标签 string | slot
  allowClear?: boolean // 可以点击清除图标删除内容
  password?: boolean // 是否启用密码框
  disabled?: boolean // 是否禁用
  maxlength?: number // 最大长度
  showCount?: boolean // 是否展示字数
  size?: 'large'|'middle'|'small' // 输入框大小
  prefix?: string // 前缀图标 string | slot
  suffix?: string // 后缀图标 string | slot
  value?: string // 输入框内容(v-model)
  valueModifiers?: object // 用于访问组件的v-model上添加的修饰符
}
const props = withDefaults(defineProps<Props>(), {
  width: '100%',
  addonBefore: '',
  addonAfter: '',
  allowClear: false,
  password: false,
  disabled: false,
  maxlength: undefined,
  showCount: false,
  size: 'middle',
  prefix: '',
  suffix: '',
  value: '',
  valueModifiers: () => ({})
})
const inputWidth = computed(() => {
  if (typeof props.width === 'number') {
    return props.width + 'px'
  }
  return props.width
})
const showCountNum = computed(() => {
  if (props.maxlength) {
    return props.value.length + ' / ' + props.maxlength
  }
  return props.value.length
})
const showPassword = ref(false)
const prefixRef = ref()
const showPrefix = ref(1)
const suffixRef = ref()
const showSuffix = ref(1)
const beforeRef = ref()
const showBefore = ref(1)
const afterRef = ref()
const showAfter = ref(1)
onMounted(() => {
  showPrefix.value = prefixRef.value.offsetWidth
  showSuffix.value = suffixRef.value.offsetWidth
  showBefore.value = beforeRef.value.offsetWidth
  showAfter.value = afterRef.value.offsetWidth
})
const emits = defineEmits(['update:value', 'change', 'enter'])
function onInput (e: any) {
  if (!('lazy' in props.valueModifiers)) {
    emits('update:value', e.target.value)
    emits('change', e)
  }
}
function onChange (e: any) {
  if ('lazy' in props.valueModifiers) {
    emits('update:value', e.target.value)
    emits('change', e)
  }
}
function onKeyboard (e: any) {
  if (e.key === 'Enter') {
    emits('enter', e)
  }
}
const input = ref()
function onClear () {
  emits('update:value', '')
  input.value.focus()
}
function onPassword () {
  showPassword.value = !showPassword.value
}
</script>
<template>
  <div class="m-input-wrap" :style="`width: ${inputWidth};`">
    <span class="m-addon" :class="{before: showBefore}" ref="beforeRef" v-if="showBefore!==23">
      <slot name="addonBefore">{{ addonBefore }}</slot>
    </span>
    <div
      class="m-input"
      :class="[`${size}`, {disabled: disabled, 'input-before': showBefore!==23, 'input-after': showAfter!==23}]"
      tabindex="1">
      <span class="m-prefix" ref="prefixRef" v-if="showPrefix">
        <slot name="prefix">{{ prefix }}</slot>
      </span>
      <input
        class="u-input"
        ref="input"
        :type="password && !showPassword ? 'password':'text'"
        :value="value"
        :maxlength="maxlength"
        :disabled="disabled"
        @input="onInput"
        @change="onChange"
        @keydown="onKeyboard"
        v-bind="$attrs" />
      <span class="m-suffix" ref="suffixRef" v-if="showSuffix">
        <span class="m-action" v-if="!disabled&&allowClear&&value" @click="onClear">
          <svg focusable="false" class="u-clear" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg>
        </span>
        <span class="m-action" v-if="password" @click="onPassword">
          <svg focusable="false" v-show="showPassword" class="u-eye" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg>
          <svg focusable="false" v-show="!showPassword" class="u-eye" data-icon="eye-invisible" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"></path><path d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"></path></svg>
        </span>
        <span class="m-count" v-if="showCount">{{ showCountNum }}</span>
        <slot name="suffix">{{ suffix }}</slot>
      </span>
    </div>
    <span class="m-addon" :class="{after: showAfter}" ref="afterRef" v-if="showAfter!==23">
      <slot name="addonAfter">{{ addonAfter }}</slot>
    </span>
  </div>
</template>
<style lang="less" scoped>
.m-input-wrap {
  width: 100%;
  text-align: start;
  vertical-align: top;
  position: relative;
  display: inline-table;
  border-collapse: separate;
  border-spacing: 0;
  .m-addon {
    position: relative;
    padding: 0 11px;
    color: rgba(0, 0, 0, 0.88);
    font-weight: normal;
    font-size: 14px;
    text-align: center;
    background-color: rgba(0, 0, 0, 0.02);
    border: 1px solid #d9d9d9;
    border-radius: 6px;
    transition: all 0.3s;
    line-height: 1;
    display: table-cell;
    width: 1px;
    white-space: nowrap;
    vertical-align: middle;
  }
  .before {
    border-start-end-radius: 0;
    border-end-end-radius: 0;
    border-inline-end: 0;
  }
  .after {
    border-start-start-radius: 0;
    border-end-start-radius: 0;
    border-inline-start: 0;
  }
  .m-input {
    font-size: 14px;
    color: rgba(0, 0, 0, 0.88);
    line-height: 1.5714285714285714;
    position: relative;
    display: inline-flex;
    width: 100%;
    min-width: 0;
    background-color: #ffffff;
    border: 1px solid #d9d9d9;
    transition: all 0.2s;
    &:hover {
      border-color: #4096ff;
      border-inline-end-width: 1px;
      z-index: 1;
    }
    &:focus-within {
      border-color: #4096ff;
      box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
      border-inline-end-width: 1px;
      outline: 0;
    }
    .m-prefix {
      margin-inline-end: 4px;
      display: flex;
      flex: none;
      align-items: center;
    }
    .u-input {
      font-size: 14px;
      line-height: 1.5714285714285714;
      position: relative;
      display: inline-block;
      width: 100%;
      min-width: 0;
      background-color: #ffffff;
      border: none;
      outline: none;
      text-overflow: ellipsis;
      transition: all 0.2s;
    }
    input:disabled {
      color: rgba(0, 0, 0, 0.25);
    }
    input::-webkit-input-placeholder {
      color: rgba(0, 0, 0, 0.25)
    }
    input:-moz-placeholder {
      color: rgba(0, 0, 0, 0.25)
    }
    input::-moz-placeholder {
      color: rgba(0, 0, 0, 0.25)
    }
    input:-ms-input-placeholder {
      color: rgba(0, 0, 0, 0.25)
    }
    .m-suffix {
      margin-inline-start: 4px;
      display: flex;
      flex: none;
      align-items: center;
      span {
        margin-right: 4px;
      }
      .m-action {
        cursor: pointer;
        .u-clear {
          font-size: 12px;
          display: inline-block;
          fill: rgba(0, 0, 0, 0.25);
          text-align: center;
          line-height: 0;
          vertical-align: -0.08em;
          transition: fill 0.3s;
          &:hover {
            fill: rgba(0, 0, 0, 0.45);
          }
        }
        .u-eye {
          font-size: 14px;
          display: inline-block;
          fill: rgba(0, 0, 0, 0.45);
          text-align: center;
          line-height: 1;
          vertical-align: -0.125em;
          transition: fill 0.3s;
          &:hover {
            fill: rgba(0, 0, 0, 0.85);
          }
        }
      }
      .m-count {
        color: rgba(0, 0, 0, 0.45);
      }
    }
  }
  .large {
    padding: 7px 11px;
    font-size: 16px;
    line-height: 1.5;
    border-radius: 8px;
  }
  .middle {
    padding: 4px 11px;
    border-radius: 6px;
  }
  .small {
    padding: 0px 7px;
    border-radius: 4px;
  }
  .input-before {
    border-start-start-radius: 0;
    border-end-start-radius: 0;
  }
  .input-after {
    border-start-end-radius: 0;
    border-end-end-radius: 0;
  }
  .disabled {
    color: rgba(0, 0, 0, 0.25);
    background-color: rgba(0, 0, 0, 0.04);
    cursor: not-allowed;
    &:hover {
      border-color: #d9d9d9;
    }
    &:focus-within {
      border-color: #d9d9d9;
      box-shadow: none
    }
    .u-input {
      background-color: transparent;
      cursor: not-allowed;
    }
  }
}
</style>

在要使用的页面引入

其中引入使用了 Vue3间距(Space)

<script setup lang="ts">
import Input from './Input.vue'
import { ref, watchEffect } from 'vue'
const value = ref('')
const lazyValue = ref('')
watchEffect(() => {
  console.log('value:', value.value)
})
watchEffect(() => {
  console.log('lazyValue:', lazyValue.value)
})
function onChange (e: Event) {
  console.log('change e:', e)
}
function onEnter (e: KeyboardEvent) {
  console.log('enter e:', e)
}
</script>
<template>
  <div>
    <h1>Input 输入框</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Space direction="vertical">
      <Input
        v-model:value="value"
        placeholder="Basic usage"
        @change="onChange"
        @enter="onEnter" />
      <Input
        v-model:value.lazy="lazyValue"
        placeholder="Lazy usage"
        @change="onChange" />
    </Space>
    <h2 class="mt30 mb10">前缀和后缀</h2>
    <Space direction="vertical">
      <Input v-model:value="value" placeholder="Basic usage">
        <template #prefix>
          <svg focusable="false" class="u-svg" data-icon="user" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"></path></svg>
        </template>
        <template #suffix>
          <Tooltip :max-width="150" title="Extra information">
            <svg focusable="false" class="u-svg" data-icon="info-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path><path d="M464 336a48 48 0 1096 0 48 48 0 10-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"></path></svg>
          </Tooltip>
        </template>
      </Input>
      <Input v-model:value="value" prefix="¥" suffix="RMB" />
    </Space>
    <h2 class="mt30 mb10">三种大小</h2>
    <Space direction="vertical">
      <Input
        size="large"
        :width="500"
        show-count
        :maxlength="20"
        allow-clear
        v-model:value="value"
        placeholder="large size"
        prefix="¥"
        suffix="RMB"
        addon-before="Http://"
        addon-after=".com" />
      <Input
        :width="500"
        show-count
        :maxlength="20"
        allow-clear
        v-model:value="value"
        placeholder="default size"
        prefix="¥"
        suffix="RMB"
        addon-before="Http://"
        addon-after=".com" />
      <Input
        size="small"
        :width="500"
        show-count
        :maxlength="20"
        allow-clear
        v-model:value="value"
        placeholder="small size"
        prefix="¥"
        suffix="RMB"
        addon-before="Http://"
        addon-after=".com" />
    </Space>
    <h2 class="mt30 mb10">前置/后置标签</h2>
    <Space direction="vertical">
      <Input
        :width="300"
        disabled
        show-count
        :maxlength="20"
        v-model:value="value"
        placeholder="Basic usage"
        prefix="¥"
        suffix="RMB"
        addon-before="Http://"
        addon-after=".com" />
      <Input v-model:value="value">
        <template #addonAfter>
          <svg focusable="false" class="u-svg" data-icon="setting" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"></path></svg>
        </template>
      </Input>
    </Space>
    <h2 class="mt30 mb10">带移除图标</h2>
    <Space>
      <Input allow-clear v-model:value="value" placeholder="input with clear icon" suffix="RMB" />
    </Space>
    <h2 class="mt30 mb10">密码框</h2>
    <Space>
      <Input password allow-clear v-model:value="value" suffix="RMB" placeholder="input password" />
    </Space>
    <h2 class="mt30 mb10">带数字提示</h2>
    <Space>
      <Input show-count allow-clear v-model:value="value" suffix="RMB"/>
    </Space>
    <h2 class="mt30 mb10">禁用</h2>
    <Space>
      <Input disabled v-model:value="value" suffix="RMB"/>
    </Space>
  </div>
</template>
<style lang="less" scoped>
.u-svg {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  text-align: center;
  vertical-align: -0.125em;
  fill: rgba(0, 0, 0, 0.88);
}
</style>

你可能感兴趣的:(vue3,ts,less,Vue3,typescript)