123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- /**
- * 时间服务模块
- * 提供准确的服务器时间,避免依赖设备本地时间和时区
- */
- import { getServerTime } from "@/api/oa/attendance/clockIn";
-
- class TimeService {
- constructor() {
- // 服务器基准时间信息
- this.serverBaseTime = {
- year: 0,
- month: 0,
- day: 0,
- hour: 0,
- minute: 0,
- second: 0,
- timestamp: 0
- };
- // 本地性能计时器基准点(performance.now()的值)
- this.performanceTimeBase = 0;
- // 最后一次同步的performance时间
- this.lastSyncPerformanceTime = 0;
- // 同步间隔(5分钟)
- this.syncInterval = 5 * 60 * 1000;
- // 是否正在同步
- this.syncing = false;
- // 是否已成功同步过服务器时间
- this.hasSyncedServer = false;
- // 时间更新回调函数列表
- this.updateCallbacks = [];
- // 更新循环是否已启动
- this.updateLoopStarted = false;
- }
-
- /**
- * 解析服务器时间字符串为时间组件
- * @param {string} timeString 时间字符串 (YYYY-MM-DD HH:mm:ss 格式)
- * @returns {Object} 时间组件对象
- */
- parseTimeString(timeString) {
- const parts = timeString.split(' ');
- const datePart = parts[0].split('-');
- const timePart = parts[1].split(':');
-
- return {
- year: parseInt(datePart[0]),
- month: parseInt(datePart[1]),
- day: parseInt(datePart[2]),
- hour: parseInt(timePart[0]),
- minute: parseInt(timePart[1]),
- second: parseInt(timePart[2]),
- timestamp: new Date(timeString.replace(/-/g, '/')).getTime()
- };
- }
-
- /**
- * 格式化时间组件为字符串
- * @param {Object} timeComponents 时间组件
- * @returns {string} 格式化的时间字符串
- */
- formatTimeComponents(timeComponents) {
- const pad = (num) => String(num).padStart(2, '0');
- return `${timeComponents.year}-${pad(timeComponents.month)}-${pad(timeComponents.day)} ${pad(timeComponents.hour)}:${pad(timeComponents.minute)}:${pad(timeComponents.second)}`;
- }
-
- /**
- * 给时间组件添加毫秒数(纯数学计算,不依赖Date对象)
- * @param {Object} baseTime 基准时间组件
- * @param {number} milliseconds 要添加的毫秒数
- * @returns {Object} 新的时间组件
- */
- addMilliseconds(baseTime, milliseconds) {
- // 复制基准时间
- let result = { ...baseTime };
-
- // 将毫秒转换为秒并添加
- const totalSeconds = Math.floor(milliseconds / 1000);
- result.second += totalSeconds;
-
- // 处理秒的进位
- if (result.second >= 60) {
- const additionalMinutes = Math.floor(result.second / 60);
- result.second = result.second % 60;
- result.minute += additionalMinutes;
- }
-
- // 处理分钟的进位
- if (result.minute >= 60) {
- const additionalHours = Math.floor(result.minute / 60);
- result.minute = result.minute % 60;
- result.hour += additionalHours;
- }
-
- // 处理小时的进位
- if (result.hour >= 24) {
- const additionalDays = Math.floor(result.hour / 24);
- result.hour = result.hour % 24;
- result.day += additionalDays;
-
- // 处理跨月、跨年的复杂情况(简化处理)
- // 这里可以根据需要进一步完善
- result = this.handleDayOverflow(result);
- }
-
- // 更新时间戳
- result.timestamp = baseTime.timestamp + milliseconds;
-
- return result;
- }
-
- /**
- * 处理日期的进位(月、年)
- * @param {Object} timeComponents 时间组件
- * @returns {Object} 处理后的时间组件
- */
- handleDayOverflow(timeComponents) {
- const daysInMonth = this.getDaysInMonth(timeComponents.year, timeComponents.month);
-
- if (timeComponents.day > daysInMonth) {
- const monthsToAdd = Math.floor((timeComponents.day - 1) / daysInMonth);
- timeComponents.day = ((timeComponents.day - 1) % daysInMonth) + 1;
- timeComponents.month += monthsToAdd;
-
- if (timeComponents.month > 12) {
- const yearsToAdd = Math.floor((timeComponents.month - 1) / 12);
- timeComponents.month = ((timeComponents.month - 1) % 12) + 1;
- timeComponents.year += yearsToAdd;
- }
- }
-
- return timeComponents;
- }
-
- /**
- * 获取指定年月的天数
- * @param {number} year 年份
- * @param {number} month 月份 (1-12)
- * @returns {number} 该月的天数
- */
- getDaysInMonth(year, month) {
- // 使用数组而不是Date对象来避免时区问题
- const daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
-
- if (month === 2 && this.isLeapYear(year)) {
- return 29;
- }
-
- return daysInMonths[month - 1];
- }
-
- /**
- * 判断是否为闰年
- * @param {number} year 年份
- * @returns {boolean} 是否为闰年
- */
- isLeapYear(year) {
- return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
- }
-
- /**
- * 同步服务器时间
- * @returns {Promise<boolean>} 同步是否成功
- */
- async syncServerTime() {
- if (this.syncing) {
- return false;
- }
-
- this.syncing = true;
-
- try {
- const syncStartPerformance = performance.now();
- const response = await getServerTime();
- const syncEndPerformance = performance.now();
-
- if (response.code === 200 && response.data) {
- console.log(response.data);
-
- // 计算网络延迟
- const networkDelay = (syncEndPerformance - syncStartPerformance) / 2;
-
- // 解析服务器时间并添加网络延迟补偿
- this.serverBaseTime = this.parseTimeString(response.data);
- this.serverBaseTime = this.addMilliseconds(this.serverBaseTime, networkDelay);
-
- // 记录performance基准时间点
- this.performanceTimeBase = syncEndPerformance;
- this.lastSyncPerformanceTime = syncEndPerformance;
- this.hasSyncedServer = true;
-
- console.log("服务器时间同步成功", {
- serverTime: this.formatTimeComponents(this.serverBaseTime),
- performanceBase: this.performanceTimeBase,
- networkDelay,
- });
-
- return true;
- }
- } catch (error) {
- console.warn("服务器时间同步失败,将使用本地时间:", error.message);
- } finally {
- this.syncing = false;
- }
-
- return false;
- }
-
- /**
- * 获取当前时间组件
- * @returns {Object} 当前时间组件
- */
- getCurrentTimeComponents() {
- const currentPerformanceTime = performance.now();
-
- // 检查是否需要重新同步(基于performance时间)
- if (this.hasSyncedServer &&
- currentPerformanceTime - this.lastSyncPerformanceTime > this.syncInterval) {
- // 异步同步,不阻塞当前调用
- this.syncServerTime();
- }
-
- // 如果已同步过服务器时间,使用服务器时间基准
- if (this.hasSyncedServer) {
- // 计算从基准点开始经过的时间
- const elapsedTime = currentPerformanceTime - this.performanceTimeBase;
- return this.addMilliseconds(this.serverBaseTime, elapsedTime);
- }
-
- // 降级到本地时间(解析当前本地时间)
- const now = new Date();
- const year = now.getFullYear();
- const month = now.getMonth() + 1;
- const day = now.getDate();
- const hour = now.getHours();
- const minute = now.getMinutes();
- const second = now.getSeconds();
-
- return {
- year, month, day, hour, minute, second,
- timestamp: now.getTime()
- };
- }
-
- /**
- * 获取当前准确时间(兼容性方法)
- * @returns {Date} 当前时间Date对象
- */
- getCurrentTime() {
- const timeComponents = this.getCurrentTimeComponents();
- return new Date(timeComponents.timestamp);
- }
-
- /**
- * 获取格式化的当前时间字符串(用于显示)
- * @returns {string} HH:mm:ss 格式的时间
- */
- getCurrentTimeString() {
- const timeComponents = this.getCurrentTimeComponents();
- const pad = (num) => String(num).padStart(2, "0");
- return `${pad(timeComponents.hour)}:${pad(timeComponents.minute)}:${pad(timeComponents.second)}`;
- }
-
- /**
- * 获取完整的当前日期时间字符串(用于提交)
- * @returns {string} YYYY-MM-DD HH:mm:ss 格式的时间
- */
- getCurrentDateTimeString() {
- const timeComponents = this.getCurrentTimeComponents();
- const pad = (num) => String(num).padStart(2, "0");
-
- return `${timeComponents.year}-${pad(timeComponents.month)}-${pad(timeComponents.day)} ${pad(timeComponents.hour)}:${pad(timeComponents.minute)}:${pad(timeComponents.second)}`;
- }
-
- /**
- * 比较当前时间与计划时间
- * @param {string} scheduleTime 计划时间 (HH:mm格式)
- * @returns {number} 比较结果 (-1: 早于, 0: 等于, 1: 晚于)
- */
- compareTimeWithSchedule(scheduleTime) {
- const timeComponents = this.getCurrentTimeComponents();
- const [scheduleHour, scheduleMinute] = scheduleTime.split(":").map(Number);
-
- const currentMinutes = timeComponents.hour * 60 + timeComponents.minute;
- const scheduleMinutes = scheduleHour * 60 + scheduleMinute;
-
- return Math.sign(currentMinutes - scheduleMinutes);
- }
-
- /**
- * 初始化时间服务
- * @returns {Promise<boolean>} 返回初始同步是否成功
- */
- async initialize() {
- const syncSuccess = await this.syncServerTime();
-
- // 使用基于performance时间的定期同步检查
- // 而不是依赖系统时间的setInterval
- this.startPeriodicSync();
-
- return syncSuccess;
- }
-
- /**
- * 启动基于performance时间的定期同步
- * 避免受设备时间修改影响
- */
- startPeriodicSync() {
- const checkSync = () => {
- const currentPerformanceTime = performance.now();
-
- // 如果距离上次同步超过间隔时间,执行同步
- if (this.hasSyncedServer &&
- currentPerformanceTime - this.lastSyncPerformanceTime > this.syncInterval) {
- this.syncServerTime();
- }
-
- // 每30秒检查一次是否需要同步
- setTimeout(checkSync, 30 * 1000);
- };
-
- // 30秒后开始第一次检查
- setTimeout(checkSync, 30 * 1000);
- }
-
- /**
- * 添加时间更新回调
- * @param {Function} callback 回调函数
- * @returns {Function} 用于移除回调的函数
- */
- onTimeUpdate(callback) {
- this.updateCallbacks.push(callback);
-
- // 启动更新循环(如果还没启动)
- if (!this.updateLoopStarted) {
- this.startUpdateLoop();
- }
-
- // 返回移除回调的函数
- return () => {
- const index = this.updateCallbacks.indexOf(callback);
- if (index > -1) {
- this.updateCallbacks.splice(index, 1);
- }
- };
- }
-
- /**
- * 启动基于requestAnimationFrame的更新循环
- * 确保稳定的1秒更新频率,不受系统时间影响
- */
- startUpdateLoop() {
- if (this.updateLoopStarted) return;
-
- this.updateLoopStarted = true;
- let lastUpdateTime = performance.now();
-
- const updateLoop = () => {
- const currentTime = performance.now();
-
- // 每秒触发一次更新(基于performance时间)
- if (currentTime - lastUpdateTime >= 1000) {
- // 执行所有回调
- this.updateCallbacks.forEach(callback => {
- try {
- callback();
- } catch (error) {
- console.error('时间更新回调执行失败:', error);
- }
- });
-
- lastUpdateTime = currentTime;
- }
-
- requestAnimationFrame(updateLoop);
- };
-
- requestAnimationFrame(updateLoop);
- }
- }
-
- // 创建全局时间服务实例
- const timeService = new TimeService();
-
- export default timeService;
|