Vue3输入框(Input)

简介: 这是一个基于 Vue 的输入框组件库,提供了丰富的自定义选项与功能。通过参数设置可以调整输入框的尺寸、前后缀图标及标签等,并支持密码输入、显示字数统计、禁用状态等功能。

效果如下图:在线预览

Input

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

Events

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

创建输入框组件Input.vue

<script setup lang="ts">
defineOptions({
   
  inheritAttrs: false
})
import {
    ref, computed, nextTick } from 'vue'
import {
    useSlotsExist } from '../utils'
interface Props {
   
  width?: string | number // 输入框宽度,单位 px
  size?: 'small' | 'middle' | 'large' // 输入框大小
  addonBefore?: string // 设置前置标签 string | slot
  addonAfter?: string // 设置后置标签 string | slot
  prefix?: string // 前缀图标 string | slot
  suffix?: string // 后缀图标 string | slot
  allowClear?: boolean // 可以点击清除图标删除内容
  password?: boolean // 是否启用密码框
  disabled?: boolean // 是否禁用
  maxlength?: number // 最大长度
  showCount?: boolean // 是否展示字数
  value?: string // (v-model) 输入框内容
  valueModifiers?: object // 用于访问组件的 v-model 上添加的修饰符
}
const props = withDefaults(defineProps<Props>(), {
   
  width: '100%',
  size: 'middle',
  addonBefore: undefined,
  addonAfter: undefined,
  prefix: undefined,
  suffix: undefined,
  allowClear: false,
  password: false,
  disabled: false,
  maxlength: undefined,
  showCount: false,
  value: undefined,
  valueModifiers: () => ({
   })
})
const inputWidth = computed(() => {
   
  if (typeof props.width === 'number') {
   
    return props.width + 'px'
  }
  return props.width
})
const showClear = computed(() => {
   
  return !props.disabled && props.allowClear
})
const showCountNum = computed(() => {
   
  if (props.maxlength) {
   
    return (props.value ? props.value.length : 0) + ' / ' + props.maxlength
  }
  return props.value ? props.value.length : 0
})
const slotsExist = useSlotsExist(['prefix', 'suffix', 'addonBefore', 'addonAfter'])
const showPrefix = computed(() => {
   
  return slotsExist.prefix || props.prefix
})
const showSuffix = computed(() => {
   
  return slotsExist.suffix || props.suffix
})
const showInputSuffix = computed(() => {
   
  return showClear.value || props.password || props.showCount || showSuffix.value
})
const showBefore = computed(() => {
   
  return slotsExist.addonBefore || props.addonBefore
})
const showAfter = computed(() => {
   
  return slotsExist.addonAfter || props.addonAfter
})
const lazyInput = computed(() => {
   
  return 'lazy' in props.valueModifiers
})
const emits = defineEmits(['update:value', 'change', 'enter'])
function onInput(e: InputEvent) {
   
  if (!lazyInput.value) {
   
    emits('update:value', (e.target as HTMLInputElement).value)
    emits('change', e)
  }
}
function onChange(e: InputEvent) {
   
  if (lazyInput.value) {
   
    emits('update:value', (e.target as HTMLInputElement).value)
    emits('change', e)
  }
}
function onKeyboard(e: KeyboardEvent) {
   
  emits('enter', e)
  if (lazyInput.value) {
   
    input.value.blur()
    nextTick(() => {
   
      input.value.focus()
    })
  }
}
const input = ref()
function onClear() {
   
  emits('update:value', '')
  input.value.focus()
}
const showPassword = ref(false)
function onPassword() {
   
  showPassword.value = !showPassword.value
}
</script>
<template>
  <div class="m-input-wrap" :style="`width: ${inputWidth};`">
    <span v-if="showBefore" class="m-addon" :class="{ 'addon-before': showBefore }">
      <slot name="addonBefore">{
   {
    addonBefore }}</slot>
    </span>
    <div
      tabindex="1"
      class="m-input"
      :class="[
        `input-${
     size}`,
        {
   
          'input-before': showBefore,
          'input-after': showAfter,
          'input-disabled': disabled
        }
      ]"
    >
      <span v-if="showPrefix" class="input-prefix">
        <slot name="prefix">{
   {
    prefix }}</slot>
      </span>
      <input
        ref="input"
        class="u-input"
        :type="password && !showPassword ? 'password' : 'text'"
        :value="value"
        :maxlength="maxlength"
        :disabled="disabled"
        @input="onInput"
        @change="onChange"
        @keydown.enter.prevent="onKeyboard"
        v-bind="$attrs"
      />
      <span v-if="showInputSuffix" class="input-suffix">
        <span v-if="showClear" class="m-actions" :class="{ 'clear-hidden': !value }" @click="onClear">
          <svg
            class="clear-svg"
            focusable="false"
            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 v-if="password" class="m-actions" @click="onPassword">
          <svg
            v-show="showPassword"
            class="eye-svg"
            focusable="false"
            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
            v-show="!showPassword"
            class="eye-svg"
            focusable="false"
            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 v-if="showCount" class="input-count">{
   {
    showCountNum }}</span>
        <span v-if="showSuffix" class="m-suffix">
          <slot name="suffix">{
   {
    suffix }}</slot>
        </span>
      </span>
    </div>
    <span v-if="showAfter" class="m-addon" :class="{ 'addon-after': showAfter }">
      <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;
    line-height: 1;
    display: table-cell;
    width: 1px;
    white-space: nowrap;
    vertical-align: middle;
    transition: all 0.3s;
    :deep(svg) {
   
      fill: rgba(0, 0, 0, 0.88);
    }
  }
  .addon-before {
   
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    border-right: 0;
  }
  .addon-after {
   
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    border-left: 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-right-width: 1px;
      z-index: 1;
    }
    &:focus-within {
   
      border-color: #4096ff;
      box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
      border-right-width: 1px;
      outline: 0;
    }
    .input-prefix {
   
      margin-right: 4px;
      display: flex;
      flex: none;
      align-items: center;
      :deep(svg) {
   
        fill: rgba(0, 0, 0, 0.88);
      }
    }
    .u-input {
   
      font-size: 14px;
      color: rgba(0, 0, 0, 0.88);
      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::-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);
    }
    .input-suffix {
   
      margin-left: 4px;
      display: flex;
      flex: none;
      gap: 8px;
      align-items: center;
      .m-actions {
   
        cursor: pointer;
        .clear-svg {
   
          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);
          }
        }
        .eye-svg {
   
          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);
          }
        }
      }
      .clear-hidden {
   
        visibility: hidden;
      }
      .input-count {
   
        color: rgba(0, 0, 0, 0.45);
      }
      .m-suffix {
   
        display: flex;
        flex: none;
        align-items: center;
        :deep(svg) {
   
          fill: rgba(0, 0, 0, 0.88);
        }
      }
    }
  }
  .input-small {
   
    padding: 0 7px;
    border-radius: 4px;
  }
  .input-middle {
   
    padding: 4px 11px;
    border-radius: 6px;
  }
  .input-large {
   
    padding: 7px 11px;
    font-size: 16px;
    line-height: 1.5;
    border-radius: 8px;
    .u-input {
   
      font-size: 16px;
      line-height: 1.5;
    }
  }
  .input-before {
   
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
  }
  .input-after {
   
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
  }
  .input-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 {
   
      color: rgba(0, 0, 0, 0.25);
      background-color: transparent;
      cursor: not-allowed;
    }
  }
}
</style>

在要使用的页面引入

<script setup lang="ts">
import Input from './Input.vue'
import {
    ref, watchEffect } from 'vue'
import {
    UserOutlined, InfoCircleOutlined, SettingOutlined, EnvironmentOutlined, CompassOutlined } from '@ant-design/icons-vue'
const value = ref('')
const lazyValue = ref('')
const sizeOptions = [
  {
   
    label: 'small',
    value: 'small'
  },
  {
   
    label: 'middle',
    value: 'middle'
  },
  {
   
    label: 'large',
    value: 'large'
  }
]
const size = ref('middle')
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>{
   {
    $route.name }} {
   {
    $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Space gap="small" vertical>
      <Alert>
        <template #message>
          .lazy:
          <br />
          默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)<br />
          你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:
          <br />
          {
   {
    '<Input v-model:value.lazy="msg" />' }}
        </template>
      </Alert>
      <Input :width="200" v-model:value="value" placeholder="Basic usage" @change="onChange" @enter="onEnter" />
      <Input :width="200" v-model:value.lazy="lazyValue" placeholder="Lazy usage" @change="onChange" @enter="onEnter" />
    </Space>
    <h2 class="mt30 mb10">前缀和后缀</h2>
    <Space gap="small" vertical :width="300">
      <Input v-model:value="value" placeholder="Basic usage" prefix="¥" suffix="RMB" />
      <Input v-model:value="value" placeholder="Basic usage">
        <template #prefix>
          <UserOutlined />
        </template>
        <template #suffix>
          <Tooltip :max-width="150" tooltip="Extra information">
            <InfoCircleOutlined />
          </Tooltip>
        </template>
      </Input>
    </Space>
    <h2 class="mt30 mb10">前置和后置标签</h2>
    <Space gap="small" vertical :width="300">
      <Input
        v-model:value="value"
        placeholder="Basic usage"
        addon-before="Http://"
        addon-after=".com"
      />
      <Input v-model:value="value" placeholder="Basic usage">
        <template #addonAfter>
          <SettingOutlined />
        </template>
      </Input>
    </Space>
    <h2 class="mt30 mb10">三种大小</h2>
    <Space gap="small" vertical :width="300">
      <Radio :options="sizeOptions" v-model:value="size" button button-style="solid" />
      <Input v-model:value="value" :size="size" placeholder="Basic usage" />
      <Input
        v-model:value="value"
        :size="size"
        placeholder="Basic usage"
      >
        <template #prefix>
          <UserOutlined />
        </template>
        <template #suffix>
          <InfoCircleOutlined />
        </template>
      </Input>
      <Input
        v-model:value="value"
        :size="size"
        placeholder="Basic usage"
      >
        <template #addonBefore>
          <EnvironmentOutlined />
        </template>
        <template #addonAfter>
          <CompassOutlined />
        </template>
      </Input>
    </Space>
    <h2 class="mt30 mb10">带清除图标</h2>
    <Space>
      <Input allow-clear v-model:value="value" placeholder="input with clear icon" />
    </Space>
    <h2 class="mt30 mb10">密码框</h2>
    <Space>
      <Input password v-model:value="value" placeholder="input password" />
    </Space>
    <h2 class="mt30 mb10">带字数提示</h2>
    <Space vertical>
      <Input show-count allow-clear v-model:value="value" placeholder="please input" />
      <Input show-count allow-clear v-model:value="value" :maxlength="20" placeholder="please input" />
    </Space>
    <h2 class="mt30 mb10">禁用</h2>
    <Space>
      <Input disabled v-model:value="value" placeholder="disabled input" />
    </Space>
  </div>
</template>
相关文章
|
13天前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
147 11
|
5月前
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
750 5
|
2月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
272 1
|
2月前
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
149 0
|
3月前
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
99 0
|
4月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
326 8
|
5月前
|
JavaScript API 容器
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
423 17
|
6月前
|
JavaScript 前端开发 算法
Vue 3 和 Vue 2 的区别及优点
Vue 3 和 Vue 2 的区别及优点
|
6月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
417 6
|
5月前
|
JavaScript 前端开发 API
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
1509 0

热门文章

最新文章