Quản lý trạng thái gửi biểu mẫu: Từ tải dữ liệu đến xử lý lỗi chi tiết

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>

Thẻ: Vue.js Element UI form-generator quản lý trạng thái API

Đăng vào ngày 10 tháng 7 lúc 17:47