Bạn đã bao giờ gặp tình huống người dùng nhấp nút gửi nhiều lần khiến dữ liệu bị trùng lặp? Bài viết này dựa trên dự án form-generator để hướng dẫn cách triển khai quản lý trạng thái gửi biểu mẫu chuyên nghiệp, bao gồm ba giải pháp cốt lõi: kiểm soát trạng thái tải, xử lý lỗi tập trung và tối ưu trải nghiệm người dùng.
Phân tích thực trạng quản lý trạng thái gửi biểu mẫu
Trong phát triển biểu mẫu truyền thống, quản lý trạng thái gửi thường bị bỏ qua, dẫn đến vấn đề về trải nghiệm người dùng. Dự án form-generator cung cấp khả năng quản lý trạng thái cơ bản thông qua các hàm gửi biểu mẫu được định sẵn.
Hiện tại, logic gửi biểu mẫu nằm trong phương thức submitForm tại tệp src/components/generator/js.js:
submitForm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
// TODO: Gửi biểu mẫu
})
}
Đoạn mã này thực hiện xác thực cơ bản nhưng thiếu logic quản lý trạng thái quan trọng, dẫn đến ba vấn đề: không có phản hồi trực quan khi gửi, không có cơ chế chống gửi trùng lặp, và không xử lý lỗi tập trung.
Giải pháp triển khai trạng thái tải
Kiểm soát trạng thái tải cơ bản
Thêm biến trạng thái submitting vào phiên bản Vue để kiểm soát trạng thái nút gửi và hiển thị hiệu ứng tải:
data() {
return {
submitting: false,
// dữ liệu khác...
}
},
methods: {
submitForm() {
this.$refs['elForm'].validate(valid => {
if (!valid) return
this.submitting = true // Bắt đầu gửi
api.submit(this.formData)
.then(() => {
this.$message.success('Gửi thành công')
})
.catch(() => {
this.$message.error('Gửi thất bại, vui lòng thử lại')
})
.finally(() => {
this.submitting = false // Đặt lại trạng thái
})
})
}
}
Kết hợp trạng thái nút và hiệu ứng tải
Liên kết trạng thái submitting trong template để kiểm soát nút bị vô hiệu hóa và hiệu ứng tải:
<el-button
type="primary"
@click="submitForm"
:loading="submitting"
:disabled="submitting"
>
Gửi
</el-button>
Thiết kế cơ chế xử lý lỗi
Xử lý lỗi xác thực biểu mẫu
form-generator đã tích hợp cơ chế xác thực biểu mẫu của Element UI thông qua hàm buildRules trong src/components/generator/js.js:
function buildRules(scheme, ruleList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const rules = []
if (ruleTrigger[config.tag]) {
if (config.required) {
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(config.defaultValue) ? `Vui lòng chọn ít nhất một ${config.label}` : scheme.placeholder
if (message === undefined) message = `${config.label} không được để trống`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
}
// các quy tắc khác...
}
}
Xử lý lỗi API tập trung
Mở rộng hàm gửi biểu mẫu để bắt và xử lý lỗi API tập trung:
submitForm() {
this.$refs['elForm'].validate(valid => {
if (!valid) return
this.submitting = true
api.submit(this.formData)
.then(response => {
if (response.code === 200) {
this.$message.success('Gửi thành công')
this.$emit('success')
} else {
this.$message.error(response.message || 'Gửi thất bại')
}
})
.catch(error => {
if (error.response) {
this.$message.error(`Lỗi: ${error.response.status} ${error.response.statusText}`)
} else if (error.request) {
this.$message.error('Lỗi mạng, vui lòng kiểm tra kết nối')
} else {
this.$message.error('Gửi thất bại, vui lòng thử lại sau')
}
})
.finally(() => {
this.submitting = false
})
})
}
Giải pháp chống gửi trùng lặp
Chống gửi trùng dựa trên trạng thái
submitForm() {
if (this.submitting) return
this.$refs['elForm'].validate(valid => {
if (!valid) return
this.submitting = true
// logic gửi...
})
}
Chống gửi trùng dựa trên định danh yêu cầu
data() {
return {
requestId: null,
}
},
methods: {
submitForm() {
this.$refs['elForm'].validate(valid => {
if (!valid) return
const requestId = Date.now()
this.requestId = requestId
this.submitting = true
api.submit(this.formData)
.then(() => {
if (this.requestId === requestId) {
this.$message.success('Gửi thành công')
}
})
.catch(() => {
if (this.requestId === requestId) {
this.$message.error('Gửi thất bại')
}
})
.finally(() => {
if (this.requestId === requestId) {
this.submitting = false
}
})
})
}
}
Ví dụ triển khai hoàn chỉnh
export default {
data() {
return {
formData: {},
rules: {},
submitting: false,
errorMessage: ''
}
},
methods: {
submitForm() {
if (this.submitting) return
this.$refs['elForm'].validate(valid => {
if (!valid) return
this.submitting = true
this.errorMessage = ''
this.mockApiSubmit(this.formData)
.then(response => {
if (response.success) {
this.$message.success('Gửi thành công')
} else {
this.errorMessage = response.message || 'Gửi thất bại'
}
})
.catch(error => {
console.error('Lỗi gửi:', error)
this.errorMessage = 'Lỗi mạng, vui lòng thử lại sau'
})
.finally(() => {
this.submitting = false
})
})
},
mockApiSubmit(data) {
return new Promise((resolve) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({ success: true })
} else {
resolve({ success: false, message: 'Mô phỏng thất bại' })
}
}, 1500)
})
}
}
}
Mẹo thực hành tốt nhất và mở rộng
Tích hợp quản lý trạng thái tập trung
Đối với ứng dụng lớn, hãy tích hợp trạng thái gửi biểu mẫu vào Vuex hoặc Pinia để chia sẻ và quản lý trạng thái xuyên thành phần.
Tối ưu hiển thị thông báo lỗi
<template>
<el-form ref="elForm" :model="formData" :rules="rules">
<div v-if="errorMessage" class="form-error-message">
{{ errorMessage }}
</div>
<el-form-item label="Họ tên" prop="name">
<el-input v-model="formData.name"></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="submitForm"
:loading="submitting"
:disabled="submitting"
>
Gửi
</el-button>
</el-form-item>
</el-form>
</template>
<style scoped>
.form-error-message {
color: #f56c6c;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #fde2e2;
background-color: #fef0f0;
border-radius: 4px;
}
</style>