/** * 时间服务模块 * 提供准确的服务器时间,避免依赖设备本地时间和时区 */ 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} 同步是否成功 */ 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} 返回初始同步是否成功 */ 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;