Vue.js實現可配置的登入表單

扎克悟空發表於2018-04-16

表單是後臺專案業務中的常用元件,這次重構了登入功能以滿足登入方式可配置的需求,在此記錄和分享一下。

業務場景

在之前,專案只支援手機號+密碼登入,前端是直接把表單寫死的,後來有客戶希望能支援驗證碼登入,有的客戶還希望能有手機號+驗證碼+密碼的登入方式...所以登入方式的靈活性需要可配置的表單支援,於是我把登入元件做了拆分。

Vue.js實現可配置的登入表單

以表單元素為粒度,分離出了手機號、密碼、簡訊驗證碼這幾個元件,它們內部都有自己的表單驗證方法,通過組合可以快速完成登入、註冊、找回密碼等表單元件。高內聚低耦合、高內聚低耦合...跟著念十遍~

.
├ common #拆分的表單元素元件
|   ├ captcha.vue  #簡訊驗證碼
|   ├ password.vue #密碼
|   └ phone.vue    #手機號
├ login #登入元件
|   └ index.vue
├ register #註冊元件
|   └ index.vue
└ resetPassword #找回密碼元件
    └ index.vue
複製程式碼

這裡我們將login作為父元件,讀取服務端返回的登入配置並在模板做條件渲染,登入時呼叫子元件內部的表單驗證,最後通過Vuex拿到資料呼叫介面。整個可配置登入表單的邏輯就是醬子,接下來上程式碼。

程式碼

請求服務端配置資料:

/* 引數說明:
 * 'password': 密碼登入 
 * 'captcha': 簡訊驗證碼登入
 * 'password_or_captcha': 密碼或簡訊登入 
 * 'password_with_captcha': 密碼+簡訊登入
 */
config: {
  login_methods: 'password'
}
複製程式碼

登入元件的核心渲染程式碼(pug):

.login-card
  .login-header
      h3 登入

  .login-content
  	 //- 手機號作為通用的賬戶名
    phone(ref="phone")
    //- 密碼元件
    password(
      v-if="isPasswordMode"
      ref="password"
    )
    //- 簡訊驗證碼元件
    captcha(
      v-if="isCaptchaMode"
      ref="captcha"
    )
    
    //- 簡訊驗證碼 和 密碼
    template(v-if="isPasswordWithCaptchaMode")
      captcha(ref="captcha")
      password(ref="password")
    
    //- 簡訊驗證碼 或 密碼
    template(v-if="isPasswordOrCaptchaMode")
      ...
    
    el-button(@click="login") 登入
複製程式碼

登入時需要三個步驟:表單驗證、組裝資料、呼叫介面:

async login () {
  if (!this.validate()) return // 表單驗證
  const loginData = this.getLoginData() // 組裝資料
  await this.postLogin(loginData) // 呼叫介面(vuex actions)
  ...
}
複製程式碼

登入的表單驗證其實是對當前登入方式中所有元件的 validate() 方法進行邏輯判斷:

/**
 * @return {Boolean} isPass
 */
validate () {
  const phone = this.$refs.phone.validate()
  let isPass = false
    
  if (this.isPasswordMode) {
    if (this.$refs.password) isPass = this.$refs.password.validate()
  }
    
  if (this.isCaptchaMode) {
    if (this.$refs.captcha) isPass = this.$refs.captcha.validate()
  }
    
  if (this.isPasswordWithCaptchaMode) ...
    
  if (this.isPasswordOrCaptchaMode) ...
    
  isPass = phone && isPass
  return isPass
}
複製程式碼

由於使用了element-ui,這裡子元件的 validate() 方法是由 el-form 元件所提供的。

可以從下面的password元件模板看到,我們將每個子元件看成一個完整的表單(當時這樣做是為了應對部分子元件內部又有多個表單元素,它們之間的互動和驗證邏輯比較複雜):

.login-password
  el-form(
    :model="form"
    :rules="rules"
    ref="form"
    @submit.native.prevent=""
  )
    el-form-item(prop="password")
      el-input(
        v-model="form.password"
        type="password"
        name="password"
      )
複製程式碼

W3C: When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.

需要注意,根據W3C標準, 當一個form元素中只有一個輸入框時,在該輸入框中按下回車會自動提交表單。通過在 <el-form> 新增 @submit.native.prevent 可以阻止這一預設行為。

password元件的表單驗證(使用element-ui表單):

/**
 * @return {Boolean} res
 */
validate () {
  let res = false
  this.$refs.form.validate((valid) => {
    res = valid
  })
  return res
}
複製程式碼

最後從Vuex裡拿到所有表單資料,進行組裝:

computed: {
  ...mapState('login', {
    phone: state => state.phone,
    password: state => state.password,
    captcha: state => state.captcha
  }),  
},
methods: {
  ...
  
  /**
   * @return {Object} data
   */
  getLoginData () {
    let mode = ''
    const phone = this.phone
    ...
    const data = { phone }
    
    if (this.isPasswordMode) {
      mode = 'password'
      data.password = password
    }
    
    if (this.isCaptchaMode) {
      mode = 'captcha'
      data.captcha = captcha
    }
    
    if (this.isPasswordWithCaptchaMode) ...
    
    if (this.isPasswordOrCaptchaMode) ...
    
    data.mode = mode
    return data
  }
}
複製程式碼

呼叫介面:

const loginData = this.getLoginData()
await this.postLogin(loginData)
...
複製程式碼

相關文章