iT邦幫忙

2024 iThome 鐵人賽

DAY 4
0
Modern Web

蓋一個自己的 Nuxt 3 UI Module系列 第 4

元件製作 input

  • 分享至 

  • xImage
  •  

輸入框狀態

當我們搞定了昨天的基本按鈕後,今天來動手實作網頁中超級重要的文字輸入框吧!
這些輸入框廣泛應用在註冊、登錄、搜尋、提交表單等等地方!
一開始先想想輸入框有哪些常見的狀態和類型🤔✨

例如:

  1. disabled 當輸入框為不可輸入的狀態
  2. label 標題 與 placeholder 提示使用者輸入資訊
  3. 錯誤提示

實作

  • step 1: 新增一個 input.vue 到 pages 資料夾下

  • step 2: 一樣先透過 interface先定義 props 會傳入的類型,然後透過 withDefaults 給予預設值

    // components/global/input.vue
    <script setup lang="ts">
    
    export interface TextInputPropsType {
      /** 預設文字 */
      placeholder?: string
      /** 欄位名稱 */
      label?: string
      /** 自訂義欄位名稱 class */
      classLabel?: string
      /** 自訂義欄位 class */
      classInput?: string
      /** input 類型 */
      type?: string
      /** name 名稱連動 for 與 id */
      name?: string
      /** 響應式 value */
      modelValue: any
      /** 錯誤訊息 (可與成功訊息並存) */
      error?: string
      /** 是否禁用 */
      disabled?: boolean
    }
    
    const props = withDefaults(defineProps<TextInputPropsType>(), {
      placeholder: ' ',
      label: undefined,
      classLabel: ' ',
      classInput: '',
      type: 'text',
      name: undefined,
      error: undefined,
      description: ''
    })
    
    defineEmits(['update:modelValue'])
    
    const types = ref(props.type)
    
    </script>
    

    以往在使用 v-model 做父子層資料的雙向綁定總是要 props+ emit 來進行傳遞,但在 Vue 3.4 版以後出現了一個大糖 defineModel !
    有了他就可以省略掉 props + emit 的過程~整個程式碼看起來更乾淨直觀了

    // components/global/input.vue
    <script setup lang="ts">
    
    export interface TextInputPropsType {
      /** 預設文字 */
      placeholder?: string
      /** 欄位名稱 */
      label?: string
      /** 自訂義欄位名稱 class */
      classLabel?: string
      /** 自訂義欄位 class */
      classInput?: string
      /** input 類型 */
      type?: string
      /** name 名稱連動 for 與 id */
      name?: string
      /** 錯誤訊息 (可與成功訊息並存) */
      error?: string
      /** 是否禁用 */
      disabled?: boolean
    }
    
    const modelValue = defineModel<string>('modelValue', {required: true})
    
    const props = withDefaults(defineProps<TextInputPropsType>(), {
      placeholder: ' ',
      label: undefined,
      classLabel: ' ',
      classInput: '',
      type: 'text',
      name: undefined,
      error: undefined,
      description: ''
    })
    const types = ref(props.type)
    
    </script>
    
    
    // components/global/input.vue
    <template>
      <div
        class="fromGroup relative items-center"
        :class="{'has-error': error}"
      >
        <label
          v-if="label"
          class="flex input-label"
          :for="name"
        >
          <span :class="classLabel">{{ label }}</span>
        </label>
        <div class="relative">
          <input
            :id="name"
            :type="types"
            :name="name"
            :placeholder="placeholder"
            :class="`${classInput} input-control w-full block focus:outline-none h-[40px]`"
            :value="modelValue"
            :error="error"
            :disabled="disabled"
            @input="$emit('update:modelValue', ($event.target as HTMLInputElement)?.value)"
          >
          <div
            class="absolute top-1/2 flex -translate-y-1/2 text-xl right-[14px]"
          >
            <span
              v-if="error"
              class="text-red-500"
            >
              <Icon name="heroicons-outline:information-circle" />
            </span>
          </div>
        </div>
    
        <span
          v-if="error"
          class="absolute -bottom-6 right-0 mt-2 text-red-500 block text-sm"
        >{{ error }}</span>
      </div>
    </template>
    <style>
    .form-control {
      @apply ...;
    }
    
    .form-label {
      @apply ...
    }
    
    .input-control {
      @apply ...
    }
    .input-label {
      @apply ...;
    }
    
    .fromGroup.has-error .input-control {
      @apply ...;
    }
    
    .input-control[disabled] {
      @apply ...
    }
    </style>
    
    
  • 完成之後的樣子是這樣

    // pages/input.vue
    <div class="flex gap-4">
        <Input
          v-model="inputValue"
          label="姓名"
          name="name"
          placeholder="請輸入姓名"
        />
        <Input
          v-model="inputValueDisabled"
          label="品種"
          name="name"
          disabled
          placeholder="請輸入品種名"
        />
        <Input
          v-model.number="inputPhone"
          label="電話"
          name="phone"
          type="text"
          placeholder="請輸入電話"
          error="無效的電話格式"
        />
      </div>
    

    image.png


上一篇
元件製作 button
下一篇
元件製作 select
系列文
蓋一個自己的 Nuxt 3 UI Module16
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言