fix(admin-spa): 修复仪表板图表时区处理问题

- 修复前端时间计算逻辑,正确处理不同时区用户的时间范围选择
- 添加系统时区信息到dashboard API响应
- 修改getSystemTimezoneDay函数,确保正确处理系统时区的日期转换
- 修复"昨天"和"前天"时间范围计算,基于系统时区而非用户时区
- 添加后端调试日志以便追踪时间转换问题

问题描述:
- 选择"昨天"时显示29号7:00到30号6:00(错误)
- 选择"近24小时"时显示29号17:00到30号17:00(错误)

修复后:
- "昨天"正确显示完整一天(如29号0:00到29号23:59)
- "近24小时"正确显示从当前时间往前24小时
- 支持所有时区的用户,不再硬编码UTC+8偏移

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-30 10:52:26 +08:00
parent 496569d110
commit a265ebf335
3 changed files with 93 additions and 70 deletions

View File

@@ -1293,7 +1293,8 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
claudeAccountsHealthy: activeClaudeAccounts > 0, claudeAccountsHealthy: activeClaudeAccounts > 0,
geminiAccountsHealthy: activeGeminiAccounts > 0, geminiAccountsHealthy: activeGeminiAccounts > 0,
uptime: process.uptime() uptime: process.uptime()
} },
systemTimezone: config.system.timezoneOffset || 8
}; };
res.json({ success: true, data: dashboard }); res.json({ success: true, data: dashboard });
@@ -1452,6 +1453,14 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
// 使用自定义时间范围 // 使用自定义时间范围
startTime = new Date(startDate); startTime = new Date(startDate);
endTime = new Date(endDate); endTime = new Date(endDate);
// 调试日志
logger.info(`📊 Usage trend hour granularity - received times:`);
logger.info(` startDate (raw): ${startDate}`);
logger.info(` endDate (raw): ${endDate}`);
logger.info(` startTime (parsed): ${startTime.toISOString()}`);
logger.info(` endTime (parsed): ${endTime.toISOString()}`);
logger.info(` System timezone offset: ${config.system.timezoneOffset || 8}`);
} else { } else {
// 默认最近24小时 // 默认最近24小时
endTime = new Date(); endTime = new Date();
@@ -1471,7 +1480,8 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
currentHour.setMinutes(0, 0, 0); currentHour.setMinutes(0, 0, 0);
while (currentHour <= endTime) { while (currentHour <= endTime) {
// 使用时区转换后的时间来生成键 // 注意前端发送的时间已经是UTC时间不需要再次转换
// 直接从currentHour生成对应系统时区的日期和小时
const tzCurrentHour = redis.getDateInTimezone(currentHour); const tzCurrentHour = redis.getDateInTimezone(currentHour);
const dateStr = redis.getDateStringInTimezone(currentHour); const dateStr = redis.getDateStringInTimezone(currentHour);
const hour = String(tzCurrentHour.getUTCHours()).padStart(2, '0'); const hour = String(tzCurrentHour.getUTCHours()).padStart(2, '0');
@@ -1545,11 +1555,11 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
hourCost = costResult.costs.total; hourCost = costResult.costs.total;
} }
// 格式化时间标签 // 格式化时间标签 - 使用系统时区的显示
const tzDate = redis.getDateInTimezone(currentHour); const tzDateForLabel = redis.getDateInTimezone(currentHour);
const month = String(tzDate.getUTCMonth() + 1).padStart(2, '0'); const month = String(tzDateForLabel.getUTCMonth() + 1).padStart(2, '0');
const day = String(tzDate.getUTCDate()).padStart(2, '0'); const day = String(tzDateForLabel.getUTCDate()).padStart(2, '0');
const hourStr = String(tzDate.getUTCHours()).padStart(2, '0'); const hourStr = String(tzDateForLabel.getUTCHours()).padStart(2, '0');
trendData.push({ trendData.push({
// 对于小时粒度只返回hour字段不返回date字段 // 对于小时粒度只返回hour字段不返回date字段

View File

@@ -18,7 +18,7 @@
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin> <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net"> <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com"> <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
<script type="module" crossorigin src="/admin-next/assets/index-8QtnjTcX.js"></script> <script type="module" crossorigin src="/admin-next/assets/index-B6gXCBSA.js"></script>
<link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js">
<link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js">
<link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js">

View File

@@ -27,7 +27,8 @@ export const useDashboardStore = defineStore('dashboard', () => {
systemRPM: 0, systemRPM: 0,
systemTPM: 0, systemTPM: 0,
systemStatus: '正常', systemStatus: '正常',
uptime: 0 uptime: 0,
systemTimezone: 8 // 默认 UTC+8
}) })
const costsData = ref({ const costsData = ref({
@@ -81,6 +82,41 @@ export const useDashboardStore = defineStore('dashboard', () => {
} }
}) })
// 辅助函数:基于系统时区计算时间
function getDateInSystemTimezone(date = new Date()) {
const offset = dashboardData.value.systemTimezone || 8
// 将本地时间转换为UTC时间然后加上系统时区偏移
const utcTime = date.getTime() + (date.getTimezoneOffset() * 60000)
return new Date(utcTime + (offset * 3600000))
}
// 辅助函数获取系统时区某一天的起止UTC时间
// 输入系统时区的日期使用getDateInSystemTimezone转换后的
// 输出对应的UTC时间
function getSystemTimezoneDay(systemTzDate, startOfDay = true) {
const offset = dashboardData.value.systemTimezone || 8
// 使用UTC方法获取系统时区日期的年月日
const year = systemTzDate.getUTCFullYear()
const month = systemTzDate.getUTCMonth()
const day = systemTzDate.getUTCDate()
// 创建一个新的Date对象
const result = new Date()
if (startOfDay) {
// 设置为系统时区的0点UTC时间
result.setUTCFullYear(year, month, day)
result.setUTCHours(0 - offset, 0, 0, 0)
} else {
// 设置为系统时区的23:59:59.999UTC时间
result.setUTCFullYear(year, month, day)
result.setUTCHours(23 - offset, 59, 59, 999)
}
return result
}
// 方法 // 方法
async function loadDashboardData() { async function loadDashboardData() {
loading.value = true loading.value = true
@@ -118,7 +154,8 @@ export const useDashboardStore = defineStore('dashboard', () => {
systemRPM: systemAverages.rpm || 0, systemRPM: systemAverages.rpm || 0,
systemTPM: systemAverages.tpm || 0, systemTPM: systemAverages.tpm || 0,
systemStatus: systemHealth.redisConnected ? '正常' : '异常', systemStatus: systemHealth.redisConnected ? '正常' : '异常',
uptime: systemHealth.uptime || 0 uptime: systemHealth.uptime || 0,
systemTimezone: dashboardResponse.data.systemTimezone || 8
} }
} }
@@ -161,28 +198,20 @@ export const useDashboardStore = defineStore('dashboard', () => {
startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000) startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000)
break break
case 'yesterday': case 'yesterday':
// 昨天:使用UTC时间避免时区双重转换 // 昨天:基于系统时区的昨天
startTime = new Date(now) const systemNow = getDateInSystemTimezone(now)
startTime.setUTCDate(now.getUTCDate() - 1) const yesterday = new Date(systemNow)
startTime.setUTCHours(0, 0, 0, 0) yesterday.setUTCDate(yesterday.getUTCDate() - 1)
endTime = new Date(startTime) startTime = getSystemTimezoneDay(yesterday, true)
endTime.setUTCHours(23, 59, 59, 999) endTime = getSystemTimezoneDay(yesterday, false)
// 由于后端会加8小时我们需要减去8小时
startTime = new Date(startTime.getTime() - 8 * 60 * 60 * 1000)
endTime = new Date(endTime.getTime() - 8 * 60 * 60 * 1000)
break break
case 'dayBefore': case 'dayBefore':
// 前天:使用UTC时间 // 前天:基于系统时区的前天
startTime = new Date(now) const systemNow2 = getDateInSystemTimezone(now)
startTime.setUTCDate(now.getUTCDate() - 2) const dayBefore = new Date(systemNow2)
startTime.setUTCHours(0, 0, 0, 0) dayBefore.setUTCDate(dayBefore.getUTCDate() - 2)
endTime = new Date(startTime) startTime = getSystemTimezoneDay(dayBefore, true)
endTime.setUTCHours(23, 59, 59, 999) endTime = getSystemTimezoneDay(dayBefore, false)
// 由于后端会加8小时我们需要减去8小时
startTime = new Date(startTime.getTime() - 8 * 60 * 60 * 1000)
endTime = new Date(endTime.getTime() - 8 * 60 * 60 * 1000)
break break
default: default:
// 默认近24小时 // 默认近24小时
@@ -249,28 +278,20 @@ export const useDashboardStore = defineStore('dashboard', () => {
startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000) startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000)
break break
case 'yesterday': case 'yesterday':
// 昨天:使用UTC时间避免时区双重转换 // 昨天:基于系统时区的昨天
startTime = new Date(now) const systemNow3 = getDateInSystemTimezone(now)
startTime.setUTCDate(now.getUTCDate() - 1) const yesterday = new Date(systemNow3)
startTime.setUTCHours(0, 0, 0, 0) yesterday.setUTCDate(yesterday.getUTCDate() - 1)
endTime = new Date(startTime) startTime = getSystemTimezoneDay(yesterday, true)
endTime.setUTCHours(23, 59, 59, 999) endTime = getSystemTimezoneDay(yesterday, false)
// 由于后端会加8小时我们需要减去8小时
startTime = new Date(startTime.getTime() - 8 * 60 * 60 * 1000)
endTime = new Date(endTime.getTime() - 8 * 60 * 60 * 1000)
break break
case 'dayBefore': case 'dayBefore':
// 前天:使用UTC时间 // 前天:基于系统时区的前天
startTime = new Date(now) const systemNow4 = getDateInSystemTimezone(now)
startTime.setUTCDate(now.getUTCDate() - 2) const dayBefore = new Date(systemNow4)
startTime.setUTCHours(0, 0, 0, 0) dayBefore.setUTCDate(dayBefore.getUTCDate() - 2)
endTime = new Date(startTime) startTime = getSystemTimezoneDay(dayBefore, true)
endTime.setUTCHours(23, 59, 59, 999) endTime = getSystemTimezoneDay(dayBefore, false)
// 由于后端会加8小时我们需要减去8小时
startTime = new Date(startTime.getTime() - 8 * 60 * 60 * 1000)
endTime = new Date(endTime.getTime() - 8 * 60 * 60 * 1000)
break break
default: default:
// 默认近24小时 // 默认近24小时
@@ -329,28 +350,20 @@ export const useDashboardStore = defineStore('dashboard', () => {
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000) startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000)
break break
case 'yesterday': case 'yesterday':
// 昨天:使用UTC时间避免时区双重转换 // 昨天:基于系统时区的昨天
startDate = new Date(now) const systemNow5 = getDateInSystemTimezone(now)
startDate.setUTCDate(now.getUTCDate() - 1) const yesterday2 = new Date(systemNow5)
startDate.setUTCHours(0, 0, 0, 0) yesterday2.setUTCDate(yesterday2.getUTCDate() - 1)
endDate = new Date(startDate) startDate = getSystemTimezoneDay(yesterday2, true)
endDate.setUTCHours(23, 59, 59, 999) endDate = getSystemTimezoneDay(yesterday2, false)
// 由于后端会加8小时我们需要减去8小时
startDate = new Date(startDate.getTime() - 8 * 60 * 60 * 1000)
endDate = new Date(endDate.getTime() - 8 * 60 * 60 * 1000)
break break
case 'dayBefore': case 'dayBefore':
// 前天:使用UTC时间 // 前天:基于系统时区的前天
startDate = new Date(now) const systemNow6 = getDateInSystemTimezone(now)
startDate.setUTCDate(now.getUTCDate() - 2) const dayBefore2 = new Date(systemNow6)
startDate.setUTCHours(0, 0, 0, 0) dayBefore2.setUTCDate(dayBefore2.getUTCDate() - 2)
endDate = new Date(startDate) startDate = getSystemTimezoneDay(dayBefore2, true)
endDate.setUTCHours(23, 59, 59, 999) endDate = getSystemTimezoneDay(dayBefore2, false)
// 由于后端会加8小时我们需要减去8小时
startDate = new Date(startDate.getTime() - 8 * 60 * 60 * 1000)
endDate = new Date(endDate.getTime() - 8 * 60 * 60 * 1000)
break break
} }
} else { } else {