表單是後臺項目業務中的經常使用組件,此次重構了登陸功能以知足登陸方式可配置的需求,在此記錄和分享一下。javascript
在以前,項目只支持手機號+密碼登陸,前端是直接把表單寫死的,後來有客戶但願能支持驗證碼登陸,有的客戶還但願能有手機號+驗證碼+密碼的登陸方式...因此登陸方式的靈活性須要可配置的表單支持,因而我把登陸組件作了拆分。html
以表單元素爲粒度,分離出了手機號、密碼、短信驗證碼這幾個組件,它們內部都有本身的表單驗證方法,經過組合能夠快速完成登陸、註冊、找回密碼等表單組件。高內聚低耦合、高內聚低耦合...跟着念十遍~前端
.
├ common #拆分的表單元素組件
| ├ captcha.vue #短信驗證碼
| ├ password.vue #密碼
| └ phone.vue #手機號
├ login #登陸組件
| └ index.vue
├ register #註冊組件
| └ index.vue
└ resetPassword #找回密碼組件
└ index.vue
複製代碼
這裏咱們將login做爲父組件,讀取服務端返回的登陸配置並在模板作條件渲染,登陸時調用子組件內部的表單驗證,最後經過Vuex拿到數據調用接口。整個可配置登陸表單的邏輯就是醬子,接下來上代碼。vue
請求服務端配置數據:java
/* 參數說明: * 'password': 密碼登陸 * 'captcha': 短信驗證碼登陸 * 'password_or_captcha': 密碼或短信登陸 * 'password_with_captcha': 密碼+短信登陸 */
config: {
login_methods: 'password'
}
複製代碼
登陸組件的核心渲染代碼(pug):vuex
.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") 登陸
複製代碼
登陸時須要三個步驟:表單驗證、組裝數據、調用接口:element-ui
async login () {
if (!this.validate()) return // 表單驗證
const loginData = this.getLoginData() // 組裝數據
await this.postLogin(loginData) // 調用接口(vuex actions)
...
}
複製代碼
登陸的表單驗證實際上是對當前登陸方式中全部組件的 validate()
方法進行邏輯判斷:bash
/** * @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
組件所提供的。async
能夠從下面的password組件模板看到,咱們將每一個子組件當作一個完整的表單(當時這樣作是爲了應對部分子組件內部又有多個表單元素,它們之間的交互和驗證邏輯比較複雜):post
.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)
...
複製代碼