Skip to content

表單元件

表單元件是使用者互動的重要部分,uni-app 提供了豐富的表單元件,用於收集和提交使用者輸入的資料。

表單元件列表

button 按鈕

按鈕元件,用於觸發操作。

屬性說明

屬性名類型預設值說明
sizeStringdefault按鈕的大小,可選值:default、mini
typeStringdefault按鈕的樣式類型,可選值:primary、default、warn
plainBooleanfalse按鈕是否鏤空,背景色透明
disabledBooleanfalse是否禁用
loadingBooleanfalse名稱前是否帶 loading 圖示
form-typeString用於 form 元件,點擊分別會觸發 form 元件的 submit/reset 事件
open-typeString開放能力,不同平台有不同的支援度
hover-classStringbutton-hover指定按鈕按下去的樣式類

範例程式碼

vue
<template>
  <view>
    <button type="primary">主要按鈕</button>
    <button type="default">預設按鈕</button>
    <button type="warn">警告按鈕</button>
    <button type="primary" loading>載入中按鈕</button>
    <button type="primary" disabled>禁用按鈕</button>
    <button type="primary" plain>鏤空按鈕</button>
  </view>
</template>

checkbox 多選框

多選框元件,用於選擇。

屬性說明

屬性名類型預設值說明
valueStringcheckbox 標識
disabledBooleanfalse是否禁用
checkedBooleanfalse目前是否選中
colorColorcheckbox 的顏色

範例程式碼

vue
<template>
  <view>
    <checkbox-group @change="checkboxChange">
      <label v-for="item in items" :key="item.value">
        <checkbox :value="item.value" :checked="item.checked" />
        {{item.name}}
      </label>
    </checkbox-group>
  </view>
</template>

<script>
export default {
  data() {
    return {
      items: [
        {value: 'USA', name: '美國', checked: false},
        {value: 'CHN', name: '中國', checked: true},
        {value: 'JPN', name: '日本', checked: false}
      ]
    }
  },
  methods: {
    checkboxChange(e) {
      console.log(e.detail.value)
    }
  }
}
</script>

input 輸入框

輸入框元件,用於文字輸入。

屬性說明

屬性名類型預設值說明
valueString輸入框的初始內容
typeStringtextinput 的類型,可選值:text、number、idcard、digit、password
passwordBooleanfalse是否是密碼類型
placeholderString輸入框為空時佔位符
placeholder-styleString指定 placeholder 的樣式
disabledBooleanfalse是否禁用
maxlengthNumber140最大輸入長度
focusBooleanfalse取得焦點
confirm-typeStringdone設定鍵盤右下角按鈕的文字,可選值:send、search、next、go、done

範例程式碼

vue
<template>
  <view>
    <input type="text" placeholder="請輸入使用者名稱" v-model="username" />
    <input type="password" placeholder="請輸入密碼" v-model="password" />
    <input type="number" placeholder="請輸入年齡" v-model="age" />
  </view>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: '',
      age: ''
    }
  }
}
</script>

picker 選擇器

選擇器元件,支援普通選擇器、多列選擇器、時間選擇器、日期選擇器。

普通選擇器

vue
<template>
  <view>
    <picker :range="array" @change="bindPickerChange">
      <view class="picker">
        目前選擇:{{array[index]}}
      </view>
    </picker>
  </view>
</template>

<script>
export default {
  data() {
    return {
      array: ['中國', '美國', '日本', '韓國'],
      index: 0
    }
  },
  methods: {
    bindPickerChange(e) {
      this.index = e.detail.value
    }
  }
}
</script>

時間選擇器

vue
<template>
  <view>
    <picker mode="time" :value="time" start="09:00" end="18:00" @change="bindTimeChange">
      <view class="picker">
        目前選擇:{{time}}
      </view>
    </picker>
  </view>
</template>

<script>
export default {
  data() {
    return {
      time: '12:00'
    }
  },
  methods: {
    bindTimeChange(e) {
      this.time = e.detail.value
    }
  }
}
</script>

日期選擇器

vue
<template>
  <view>
    <picker mode="date" :value="date" start="2015-09-01" end="2025-09-01" @change="bindDateChange">
      <view class="picker">
        目前選擇:{{date}}
      </view>
    </picker>
  </view>
</template>

<script>
export default {
  data() {
    return {
      date: '2021-09-01'
    }
  },
  methods: {
    bindDateChange(e) {
      this.date = e.detail.value
    }
  }
}
</script>

radio 單選框

單選框元件,用於單選。

屬性說明

屬性名類型預設值說明
valueStringradio 標識
checkedBooleanfalse目前是否選中
disabledBooleanfalse是否禁用
colorColorradio 的顏色

範例程式碼

vue
<template>
  <view>
    <radio-group @change="radioChange">
      <label v-for="item in items" :key="item.value">
        <radio :value="item.value" :checked="item.checked" />
        {{item.name}}
      </label>
    </radio-group>
  </view>
</template>

<script>
export default {
  data() {
    return {
      items: [
        {value: 'USA', name: '美國', checked: false},
        {value: 'CHN', name: '中國', checked: true},
        {value: 'JPN', name: '日本', checked: false}
      ]
    }
  },
  methods: {
    radioChange(e) {
      console.log(e.detail.value)
    }
  }
}
</script>

slider 滑動選擇器

滑動選擇器,用於選擇一個數值。

屬性說明

屬性名類型預設值說明
minNumber0最小值
maxNumber100最大值
stepNumber1步長,取值必須大於 0,並且可被(max - min)整除
disabledBooleanfalse是否禁用
valueNumber0目前取值
show-valueBooleanfalse是否顯示目前 value
activeColorColor#1aad19已選擇的顏色
backgroundColorColor#e9e9e9背景條的顏色
block-sizeNumber28滑塊的大小,取值範圍為 12 - 28
block-colorColor#ffffff滑塊的顏色

範例程式碼

vue
<template>
  <view>
    <slider value="50" @change="sliderChange" show-value />
  </view>
</template>

<script>
export default {
  methods: {
    sliderChange(e) {
      console.log('slider value 發生變化:' + e.detail.value)
    }
  }
}
</script>

switch 開關選擇器

開關選擇器,用於切換選中狀態。

屬性說明

屬性名類型預設值說明
checkedBooleanfalse是否選中
disabledBooleanfalse是否禁用
typeStringswitch樣式,有效值:switch, checkbox
colorColorswitch 的顏色

範例程式碼

vue
<template>
  <view>
    <switch checked @change="switchChange" />
    <switch type="checkbox" />
  </view>
</template>

<script>
export default {
  methods: {
    switchChange(e) {
      console.log('switch 發生 change 事件,攜帶值為', e.detail.value)
    }
  }
}
</script>

textarea 多行輸入框

多行輸入框,用於多行文字輸入。

屬性說明

屬性名類型預設值說明
valueString輸入框的內容
placeholderString輸入框為空時佔位符
placeholder-styleString指定 placeholder 的樣式
disabledBooleanfalse是否禁用
maxlengthNumber140最大輸入長度
focusBooleanfalse取得焦點
auto-heightBooleanfalse是否自動增高
fixedBooleanfalse如果 textarea 是在一個 position:fixed 的區域,需要顯示指定屬性 fixed 為 true

範例程式碼

vue
<template>
  <view>
    <textarea placeholder="請輸入留言" v-model="message" auto-height />
  </view>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  }
}
</script>

form 表單

表單容器,用於將表單元件內的使用者輸入的資料提交。

屬性說明

屬性名類型預設值說明
report-submitBooleanfalse是否返回 formId 用於發送範本訊息

範例程式碼

vue
<template>
  <view>
    <form @submit="formSubmit" @reset="formReset">
      <view class="form-item">
        <text>姓名:</text>
        <input name="name" placeholder="請輸入姓名" />
      </view>
      <view class="form-item">
        <text>年齡:</text>
        <input name="age" placeholder="請輸入年齡" />
      </view>
      <view class="form-buttons">
        <button type="primary" form-type="submit">提交</button>
        <button form-type="reset">重置</button>
      </view>
    </form>
  </view>
</template>

<script>
export default {
  methods: {
    formSubmit(e) {
      console.log('form發生了submit事件,攜帶資料為:', e.detail.value)
    },
    formReset(e) {
      console.log('form發生了reset事件')
    }
  }
}
</script>

<style>
.form-item {
  display: flex;
  margin-bottom: 10px;
}
.form-buttons {
  display: flex;
  justify-content: space-around;
  margin-top: 20px;
}
</style>

表單校驗

在實際應用中,表單校驗是非常重要的一環。uni-app 可以結合第三方表單校驗庫(如 async-validator)或自訂校驗邏輯來實現表單校驗。

使用 async-validator 進行表單校驗

vue
<template>
  <view class="container">
    <form @submit="submitForm">
      <view class="form-item">
        <text class="label">使用者名稱:</text>
        <input v-model="formData.username" placeholder="請輸入使用者名稱" />
        <text v-if="errors.username" class="error">{{errors.username[0]}}</text>
      </view>
      <view class="form-item">
        <text class="label">信箱:</text>
        <input v-model="formData.email" placeholder="請輸入信箱" />
        <text v-if="errors.email" class="error">{{errors.email[0]}}</text>
      </view>
      <view class="form-item">
        <text class="label">年齡:</text>
        <input v-model="formData.age" type="number" placeholder="請輸入年齡" />
        <text v-if="errors.age" class="error">{{errors.age[0]}}</text>
      </view>
      <button type="primary" form-type="submit">提交</button>
    </form>
  </view>
</template>

<script>
import Schema from 'async-validator';

export default {
  data() {
    return {
      formData: {
        username: '',
        email: '',
        age: ''
      },
      errors: {}
    }
  },
  methods: {
    submitForm() {
      // 定義校驗規則
      const rules = {
        username: [
          { required: true, message: '請輸入使用者名稱' },
          { min: 3, max: 20, message: '使用者名稱長度在 3 到 20 個字元' }
        ],
        email: [
          { required: true, message: '請輸入信箱' },
          { type: 'email', message: '請輸入正確的信箱格式' }
        ],
        age: [
          { required: true, message: '請輸入年齡' },
          { type: 'number', message: '年齡必須為數字' },
          { min: 18, max: 100, message: '年齡必須在 18 到 100 之間' }
        ]
      };
      
      // 建立校驗器
      const validator = new Schema(rules);
      
      // 執行校驗
      validator.validate(this.formData, (errors, fields) => {
        if (errors) {
          // 校驗失敗,顯示錯誤資訊
          this.errors = fields;
        } else {
          // 校驗通過,清空錯誤資訊
          this.errors = {};
          // 提交表單資料
          uni.showToast({
            title: '提交成功',
            icon: 'success'
          });
          console.log('表單資料:', this.formData);
        }
      });
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.form-item {
  margin-bottom: 20px;
}
.label {
  display: block;
  margin-bottom: 5px;
}
.error {
  color: #f00;
  font-size: 12px;
  margin-top: 5px;
}
</style>

進階表單功能

動態表單

在某些場景下,我們需要根據使用者的選擇動態顯示不同的表單欄位。

vue
<template>
  <view class="container">
    <form @submit="submitForm">
      <view class="form-item">
        <text class="label">使用者類型:</text>
        <radio-group @change="userTypeChange">
          <label>
            <radio value="personal" :checked="formData.userType === 'personal'" />
            個人使用者
          </label>
          <label>
            <radio value="enterprise" :checked="formData.userType === 'enterprise'" />
            企業使用者
          </label>
        </radio-group>
      </view>
      
      <!-- 個人使用者欄位 -->
      <template v-if="formData.userType === 'personal'">
        <view class="form-item">
          <text class="label">真實姓名:</text>
          <input v-model="formData.realName" placeholder="請輸入真實姓名" />
        </view>
        <view class="form-item">
          <text class="label">身分證號:</text>
          <input v-model="formData.idCard" placeholder="請輸入身分證號" />
        </view>
      </template>
      
      <!-- 企業使用者欄位 -->
      <template v-if="formData.userType === 'enterprise'">
        <view class="form-item">
          <text class="label">公司名稱:</text>
          <input v-model="formData.companyName" placeholder="請輸入公司名稱" />
        </view>
        <view class="form-item">
          <text class="label">統一編號:</text>
          <input v-model="formData.taxNumber" placeholder="請輸入統一編號" />
        </view>
        <view class="form-item">
          <text class="label">聯絡人:</text>
          <input v-model="formData.contactPerson" placeholder="請輸入聯絡人" />
        </view>
      </template>
      
      <button type="primary" form-type="submit">提交</button>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      formData: {
        userType: 'personal',
        realName: '',
        idCard: '',
        companyName: '',
        taxNumber: '',
        contactPerson: ''
      }
    }
  },
  methods: {
    userTypeChange(e) {
      this.formData.userType = e.detail.value
      // 清空其他類型的資料
      if (this.formData.userType === 'personal') {
        this.formData.companyName = ''
        this.formData.taxNumber = ''
        this.formData.contactPerson = ''
      } else {
        this.formData.realName = ''
        this.formData.idCard = ''
      }
    },
    submitForm() {
      console.log('提交的表單資料:', this.formData)
    }
  }
}
</script>

表單步驟導航

對於複雜的表單,可以分步驟進行填寫,提升使用者體驗。

vue
<template>
  <view class="container">
    <!-- 步驟指示器 -->
    <view class="steps">
      <view 
        v-for="(step, index) in steps" 
        :key="index"
        class="step-item"
        :class="{ active: currentStep === index, completed: currentStep > index }"
      >
        <view class="step-number">{{ index + 1 }}</view>
        <text class="step-title">{{ step.title }}</text>
      </view>
    </view>
    
    <!-- 表單內容 -->
    <form @submit="submitForm">
      <!-- 第一步:基本資訊 -->
      <view v-if="currentStep === 0" class="step-content">
        <view class="form-item">
          <text class="label">姓名:</text>
          <input v-model="formData.name" placeholder="請輸入姓名" />
        </view>
        <view class="form-item">
          <text class="label">手機號碼:</text>
          <input v-model="formData.phone" placeholder="請輸入手機號碼" />
        </view>
        <view class="form-item">
          <text class="label">信箱:</text>
          <input v-model="formData.email" placeholder="請輸入信箱" />
        </view>
      </view>
      
      <!-- 第二步:詳細資訊 -->
      <view v-if="currentStep === 1" class="step-content">
        <view class="form-item">
          <text class="label">地址:</text>
          <textarea v-model="formData.address" placeholder="請輸入詳細地址" />
        </view>
        <view class="form-item">
          <text class="label">職業:</text>
          <picker :range="occupations" @change="occupationChange">
            <view class="picker">
              {{ formData.occupation || '請選擇職業' }}
            </view>
          </picker>
        </view>
      </view>
      
      <!-- 第三步:確認資訊 -->
      <view v-if="currentStep === 2" class="step-content">
        <view class="confirm-item">
          <text class="confirm-label">姓名:</text>
          <text class="confirm-value">{{ formData.name }}</text>
        </view>
        <view class="confirm-item">
          <text class="confirm-label">手機:</text>
          <text class="confirm-value">{{ formData.phone }}</text>
        </view>
        <view class="confirm-item">
          <text class="confirm-label">信箱:</text>
          <text class="confirm-value">{{ formData.email }}</text>
        </view>
        <view class="confirm-item">
          <text class="confirm-label">地址:</text>
          <text class="confirm-value">{{ formData.address }}</text>
        </view>
        <view class="confirm-item">
          <text class="confirm-label">職業:</text>
          <text class="confirm-value">{{ formData.occupation }}</text>
        </view>
      </view>
      
      <!-- 操作按鈕 -->
      <view class="form-actions">
        <button 
          v-if="currentStep > 0" 
          @click="prevStep" 
          type="default"
        >
          上一步
        </button>
        <button 
          v-if="currentStep < steps.length - 1" 
          @click="nextStep" 
          type="primary"
        >
          下一步
        </button>
        <button 
          v-if="currentStep === steps.length - 1" 
          form-type="submit" 
          type="primary"
        >
          提交
        </button>
      </view>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      currentStep: 0,
      steps: [
        { title: '基本資訊' },
        { title: '詳細資訊' },
        { title: '確認提交' }
      ],
      formData: {
        name: '',
        phone: '',
        email: '',
        address: '',
        occupation: ''
      },
      occupations: ['學生', '上班族', '自由業', '退休', '其他']
    }
  },
  methods: {
    nextStep() {
      if (this.validateCurrentStep()) {
        this.currentStep++
      }
    },
    prevStep() {
      this.currentStep--
    },
    validateCurrentStep() {
      // 根據目前步驟進行校驗
      switch (this.currentStep) {
        case 0:
          if (!this.formData.name || !this.formData.phone || !this.formData.email) {
            uni.showToast({
              title: '請填寫完整的基本資訊',
              icon: 'none'
            })
            return false
          }
          break
        case 1:
          if (!this.formData.address || !this.formData.occupation) {
            uni.showToast({
              title: '請填寫完整的詳細資訊',
              icon: 'none'
            })
            return false
          }
          break
      }
      return true
    },
    occupationChange(e) {
      this.formData.occupation = this.occupations[e.detail.value]
    },
    submitForm() {
      uni.showToast({
        title: '提交成功',
        icon: 'success'
      })
      console.log('最終提交的資料:', this.formData)
    }
  }
}
</script>

<style>
.steps {
  display: flex;
  justify-content: space-between;
  margin-bottom: 30px;
  padding: 0 20px;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
}
.step-number {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #ddd;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 5px;
}
.step-item.active .step-number {
  background-color: #42b983;
}
.step-item.completed .step-number {
  background-color: #42b983;
}
.step-title {
  font-size: 12px;
  color: #666;
}
.step-item.active .step-title {
  color: #42b983;
}
.step-content {
  padding: 20px;
}
.confirm-item {
  display: flex;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}
.confirm-label {
  font-weight: bold;
  color: #333;
}
.confirm-value {
  color: #666;
}
.form-actions {
  display: flex;
  justify-content: space-between;
  padding: 20px;
}
.picker {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
</style>

最佳實務

  1. 表單資料統一管理:將表單資料統一放在 data 中管理,便於提交和校驗。
  2. 合理使用雙向綁定:使用 v-model 進行雙向資料綁定,簡化資料處理。
  3. 分組管理複雜表單:對於複雜表單,可以將其拆分為多個小元件,分別管理。
  4. 表單校驗前端先行:在提交到伺服器前,先在前端進行資料校驗,減輕伺服器壓力。
  5. 友好的錯誤提示:提供清晰、具體的錯誤提示,幫助使用者快速修正輸入。
  6. 適配不同平台:注意表單元件在不同平台上的表現差異,做好相容處理。
  7. 合理使用 placeholder:提供有意義的佔位符文字,引導使用者輸入。
  8. 考慮無障礙設計:為表單元素添加適當的標籤和描述,提高可存取性。

常見問題

1. 表單元件在不同平台上的差異

uni-app 的表單元件在不同平台上可能存在差異,特別是在樣式和互動方面。建議在開發時多測試,確保在各平台上都有良好的表現。

2. 鍵盤遮擋輸入框

在行動端,當使用者點擊輸入框時,鍵盤彈出可能會遮擋輸入框。可以透過調整頁面佈局或使用 adjust-position 屬性來解決。

3. 表單資料的重置

使用 form 元件的 reset 事件可以方便地重置表單資料,但需要注意的是,這只會重置表單元件的值,不會重置綁定的資料模型。如果需要同時重置資料模型,需要在 reset 事件處理函數中手動重置。

4. 表單提交時的防重複提交

為了防止使用者多次點擊提交按鈕導致重複提交,可以在提交時禁用提交按鈕,等待伺服器回應後再啟用。

vue
<template>
  <view>
    <form @submit="submitForm">
      <!-- 表單內容 -->
      <button type="primary" form-type="submit" :disabled="submitting">
        {{submitting ? '提交中...' : '提交'}}
      </button>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      submitting: false
    }
  },
  methods: {
    submitForm() {
      if (this.submitting) return;
      
      this.submitting = true;
      
      // 模擬提交請求
      setTimeout(() => {
        uni.showToast({
          title: '提交成功',
          icon: 'success'
        });
        this.submitting = false;
      }, 2000);
    }
  }
}
</script>

相關連結

一次開發,多端部署 - 讓跨平台開發更簡單