第一版实现
第二版实现
需求:
点击按钮,请求成功返回后,置灰60秒倒计时,不允许点击
与第一版相比,优化点
- 系统可以多处使用,通过自定义storagekey实现
- 简化实现逻辑,简单易读
- 解绑vue-store、mixins
- 使用hooks
代码实现
vue文件
<u-button
:disabled="remainTime > 0"
:loading="timerLoading"
type="primary"
@click="handleTriggerTaskUploadSyncReportJob"
>
<template v-if="remainTime > 0">
<u-icon type="time" />
{{ remainTime + ' ' }}
</template>
同步记录
</u-button>
import { triggerTaskUploadSyncReportJobApi } from '@/api/statistics';
import useTimer from '@/hooks/useTimer';
const {
remainTime, timerLoading, createTimer
} = useTimer({
timerName: 'transition-record',
});
// 同步记录
const handleTriggerTaskUploadSyncReportJob = () => {
timerLoading.value = true;
triggerTaskUploadSyncReportJobApi({ uploadDate: parseTime(new Date(), 'YYYY-MM-DD') }).then(
res => {
if (res) {
getList(); // 刷新列表
}
}
);
};
// 查询接口返回后执行
if (timerLoading.value) {
createTimer(true); // 主动(true)创建定时器
timerLoading.value = false;
}
return {
remainTime, // 剩余时间
timerLoading, // 同步记录按钮
}
useTimer.js
import { message } from '';
import { ref, onMounted } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
import { getRequestAnimationFrame, cancelRequestAnimationFrame } from './timer/raf';
// timerName 计时器名称,区分计时器的唯一标志
// totalSec 计时器总数,默认60s
export default function useTimer({ timerName, totalSec = 60 }) {
if (!timerName) return message.error('计时器名称不可用为空!');
// localstorage存储同步记录开始时间(点击时间)的key
const storageTimerName = `__marketing-html-timer-${timerName}__`;
// 变量
const timerLoading = ref(false); // 同步记录按钮加载状态
const remainTime = ref(0); // 剩余时间
const timerId = ref(undefined); // 计时器id
// 移除定时器
const removeTimer = () => {
cancelRequestAnimationFrame(timerId.value); // 关闭定时器
timerId.value = undefined; // 计时器id
remainTime.value = 0; // 归零剩余时间
};
// 生成剩余时间-totalSec秒内禁止点击
const generateRemainTime = () => {
if (localStorage.getItem(storageTimerName)) {
// 存在,则获取剩余时间
return (
totalSec - Math.floor((Date.now() - (localStorage.getItem(storageTimerName) || 0)) / 1000)
);
}
return 0; // 不存在剩余时间
};
// 是否执行循环方法
const checkRemainTimeFn = () => {
const genRemainTime = generateRemainTime(); // 剩余时间
// 不存在或刚结束 genRemainTime = 0
// 跳转到其他页面,或切换浏览器后,剩余时间已结束 genRemainTime < 0
if (genRemainTime <= 0) {
removeTimer(); // 清理定时器
localStorage.removeItem(storageTimerName); // 清除缓存刷新开始时间
}
// 时间改变后,同步修改剩余时间
if (remainTime.value !== genRemainTime) remainTime.value = genRemainTime;
};
// 自定义定时器
const setInterValCustom = checkRemainTimeFn => {
const raf = getRequestAnimationFrame(); // 获取定时器方法
const loop = () => {
// 循环定时器
timerId.value = raf(loop);
checkRemainTimeFn(); // 循环方法
};
timerId.value = raf(loop); // 执行动画回调
};
// 创建定时器
const createTimer = isActive => {
// isActive 是否主动触发,点击同步按钮时,主动出发,刷新页面时,如果刷新时间未结束,是被动触发
if (isActive) {
localStorage.setItem(storageTimerName, Date.now());
}
setInterValCustom(checkRemainTimeFn); // 设置定时器
};
// visibilitychange 事件函数
const visibilityChange = () => {
// document 身上有一个属性叫作 visibilityState
// 表示当前页面是显示或者隐藏状态
if (document.visibilityState === 'hidden') {
// 如果隐藏(最小化,其他网页)
// 关闭定时器
removeTimer();
} else if (document.visibilityState === 'visible') {
// 开启定时器
createTimer(false);
}
};
// 1、被动创建定时器(刷新页面后,有缓存定时器,则重新激活定时器)
// 2、监听浏览器tab切换操作
onMounted(() => {
createTimer(false); // 被动(false)创建定时器
window.document.addEventListener('visibilitychange', visibilityChange);
});
// 退出页面时,关闭定时器 解除绑定事件
onBeforeRouteLeave(() => {
removeTimer();
window.document.removeEventListener('visibilitychange', visibilityChange);
});
return {
storageTimerName, // 存储key值
remainTime, // 计时器剩余时间
timerLoading, // 按钮加载中状态
createTimer, // 启动定时器
removeTimer // 关闭定时器
};
}
raf.js
function requestAnimationFramePolyfill() {
let lastTime = 0;
return callback => {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(() => {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
export const getRequestAnimationFrame = () => {
if (window.requestAnimationFrame) {
return window.requestAnimationFrame;
}
return requestAnimationFramePolyfill();
};
export const cancelRequestAnimationFrame = id => {
if (window.cancelAnimationFrame) {
return window.cancelAnimationFrame(id);
}
return clearTimeout(id);
};
const raf = getRequestAnimationFrame();
export const cancelAnimationTimeout = frame => cancelRequestAnimationFrame(frame.id);
export const requestAnimationTimeout = (callback, delay) => {
const start = Date.now();
function timeout() {
if (Date.now() - start >= delay) {
callback();
} else {
// eslint-disable-next-line no-use-before-define
frame.id = raf(timeout);
}
}
const frame = {
id: raf(timeout)
};
return frame;
};