SKILL.md 4.1 KB

前端防重复点击最佳实践

问题描述

在Web应用中,当用户快速多次点击按钮或触发异步操作时,可能会导致:

  1. 多次发送相同的请求
  2. 数据重复提交
  3. 用户体验不佳
  4. 系统资源浪费

解决方案

在所有触发后台操作的前端事件中添加进度条和防重复点击机制。

实现方法

1. 使用loading状态控制

在按钮上绑定loading属性,在请求期间禁用按钮并显示加载状态。

<template>
  <el-button 
    :loading="loading" 
    @click="handleAction"
    :disabled="loading">
    {{ loading ? '处理中...' : '提交' }}
  </el-button>
</template>

<script>
export default {
  data() {
    return {
      loading: false
    }
  },
  methods: {
    async handleAction() {
      if (this.loading) return; // 防止重复点击
      
      this.loading = true;
      try {
        // 执行后台操作
        await this.apiCall();
      } finally {
        this.loading = false; // 确保无论成功失败都恢复状态
      }
    }
  }
}
</script>

2. 全局Loading覆盖

对于页面级别的操作,可以使用全局Loading遮罩:

methods: {
  async handleAction() {
    const loadingInstance = this.$loading({
      lock: true,
      text: '处理中...',
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.7)'
    });
    
    try {
      await this.apiCall();
    } finally {
      loadingInstance.close();
    }
  }
}

3. 防抖(debounce)和节流(throttle)技术

对于搜索框等频繁触发的操作,可以使用防抖或节流:

import { debounce } from 'lodash';

export default {
  methods: {
    // 防抖:延迟执行,适用于搜索
    search: debounce(function(query) {
      this.performSearch(query);
    }, 300),
    
    // 节流:固定频率执行,适用于滚动等
    throttleClick: throttle(function() {
      this.handleScroll();
    }, 1000)
  }
}

4. 统一组件封装

创建可复用的防重复点击组件:

<!-- ReusableButton.vue -->
<template>
  <el-button 
    v-bind="$attrs" 
    :loading="isLoading" 
    @click="handleClick"
    :disabled="isLoading || disabled">
    <slot></slot>
  </el-button>
</template>

<script>
export default {
  name: 'ReusableButton',
  props: {
    disabled: Boolean,
    apiFunction: Function,
    autoReset: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    async handleClick(event) {
      if (this.isLoading || this.disabled) return;
      
      this.isLoading = true;
      try {
        await this.apiFunction();
      } finally {
        if (this.autoReset) {
          this.isLoading = false;
        }
      }
    }
  }
}
</script>

5. 在表单提交中的应用

<template>
  <el-form :model="form" ref="formRef">
    <!-- 表单内容 -->
    <el-form-item>
      <el-button 
        type="primary" 
        :loading="submitting" 
        @click="submitForm">
        {{ submitting ? '提交中...' : '提交' }}
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      submitting: false,
      form: {}
    }
  },
  methods: {
    async submitForm() {
      if (this.submitting) return; // 防重复提交
      
      this.submitting = true;
      try {
        await this.$refs.formRef.validate();
        await this.submitToBackend(this.form);
        this.$message.success('提交成功');
      } catch (error) {
        this.$message.error('提交失败');
      } finally {
        this.submitting = false;
      }
    }
  }
}
</script>

应用场景

以下操作都应该添加防重复点击机制:

  • 表单提交
  • 数据删除
  • 文件上传/下载
  • 搜索查询
  • 状态切换
  • 导出操作
  • 任何发起网络请求的操作

注意事项

  1. 确保在finally块中重置loading状态,以防止异常情况下按钮一直处于loading状态
  2. 对于长时间操作,提供清晰的反馈信息
  3. 合理设置防抖/节流的时间间隔
  4. 在移动端尤其需要注意防重复点击,因为触摸操作更容易产生重复点击