同一时间按钮多次点击发送无效数据解决方案

问题描述

当前端使用 ajax 提交一个表单时,因网络缓慢等原因导致数据没有及时返回数据,导致用户没有得到正确的提示,以为未点中提交按钮,进而在此点击提交按钮,进行提交数据(多半会重复多次点击)

解决方法

  • 禁用按钮,并添加 loading 提示
  • 移除点击事件
  • 结合vue使用

常规点击请求代码

这里自己封装一个函数来模仿 ajax 请求

1
2
3
4
5
6
7
8
9
10
// 模拟ajax请求数据
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 随机返回错误
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 2000)
})
}

理想状态下,点击按钮应返回数据,但因为网络缓慢,所以模拟数据回来要 2s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

<button id="btn">点击</button>
<script>
// 按钮点击次数
var num = 0

// 模拟ajax请求数据
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 3000)
})
}

const btn = document.getElementById('btn')
btn.addEventListener('click', function() {
ajax().then(res=> {
console.log('数据回来了');
}).catch(e=>{
console.log('数据请求有问题呀');
})
console.log(++num);
})

</script>

结果,作为新用户的我看到数据没有显示出来,以为没点中按钮,于是又多点了几下按钮。

image-20201005110057217

解决方案

禁用按钮,添加提示 loading

代码思路:将当前点击按钮禁用,并将按钮展示文本信息进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<button id="btn">点击</button>
<p>0次</p>
<script>

// 按钮点击次数
var num = 0

// 模拟ajax请求数据
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 3000)
})
}

const btn = document.getElementById('btn')

var handleClick = function () {
// 禁用按钮
btn.setAttribute('disabled', true)
// 文字提示
btn.innerText = 'loading...'

// 点击次数,渲染到p标签上
num++
document.querySelector('p').innerText = `${num} 次`

// 模拟请求数据,并作出响应
ajax()
.then((res) => {
console.log('点击成功: ' + res)
})
.catch((e) => {
console.log('点击失败: ' + e)
})
.finally(() => {
// 数据请求完毕,恢复按钮
btn.removeAttribute('disabled')
btn.innerText = '点击'
})
}
btn.addEventListener('click', handleClick)
</script>

移除按钮点击事件

换个思路,不妨可以直接将点击事件移除,这样用户也同样不会触发多次点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<button id="btn">点击</button>
<p>0次</p>
<script>
// 按钮点击次数
var num = 0

// 模拟ajax请求数据
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 3000)
})
}

const btn = document.getElementById('btn')

var handleClick = function () {
// 移除按钮点击事件
btn.removeEventListener('click', handleClick)
btn.innerText = '加载中...'

// 点击次数
num++
document.querySelector('p').innerText = `${num} 次`

// 模拟请求数据,并作出响应
ajax()
.then((res) => {
console.log('点击成功: ' + res)
})
.catch((e) => {
console.log('点击失败: ' + e)
})
.finally(() => {
// 数据请求完毕,恢复按钮
btn.addEventListener('click', handleClick)
btn.innerText = '点击'
})
}
btn.addEventListener('click', handleClick)
</script>

以上两种方法结合使用

  • 禁用按钮
  • 添加提示
  • 移除点击事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<button id="btn">点击</button>
<p></p>
<script>
// 按钮点击次数
var num = 0

// 模拟ajax请求数据
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 3000)
})
}

const btn = document.getElementById('btn')
var handleClick = function () {
// 使按钮无效处理
btn.removeEventListener('click', handleClick)
btn.setAttribute('disabled', true)
btn.innerText = '加载中...'

// 点击次数
num++
document.querySelector('p').innerText = `${num} 次`

// 模拟请求数据,并作出响应
ajax()
.then((res) => {
console.log('点击成功: ' + res)
})
.catch((e) => {
console.log('点击失败: ' + e)
})
.finally(() => {
// 数据请求完毕,恢复按钮
btn.addEventListener('click', handleClick)
btn.removeAttribute('disabled')
btn.innerText = '点击'
})
}
btn.addEventListener('click', handleClick)
</script>

结合vue来使用

这里只是做了一个小的demo来解释下思路,多数情况下会用axios,可选择在封装axios请求时,进行封装这个操作。网上还有一些案例使用了指令来封装,思路有很多。

这里没有使用移除点击事件,可自行选择添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>防止用户同一时间内点击多次按钮,发送多次无效数据</title>
</head>
<body>
<div id="root">
<button @click="handleClick" ref="btn">点击</button>
<p></p>
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
new Vue({
el: '#root',
methods: {
// 对ajax请求封装
ajax(button) {
let text = ''
if (button && button instanceof HTMLElement) {
text = button.innerText || ''
button.setAttribute('disabled', true)
button.innerText = 'loading...'
}
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = parseInt(Math.random() * 10) % 2
res === 0 ? resolve(1) : reject(2)
}, 1000)
})
.then((res) => {
// 无论是 resolve
if (button) {
button.removeAttribute('disabled')
button.innerText = text
}
return res
})
.catch((e) => {
// 还是 catch,都能重置按钮
if (button) {
button.removeAttribute('disabled')
button.innerText = text
}
throw e
})
},
handleClick() {
// 只需要在调用的时候传入按钮
this.ajax(this.$refs.btn)
.then((res) => {
console.log('点击成功: ' + res)
})
.catch((e) => {
console.log('点击失败: ' + e)
})
},
},
})
</script>
</body>
</html>