mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
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:
@@ -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字段
|
||||||
|
|||||||
2
web/admin-spa/dist/index.html
vendored
2
web/admin-spa/dist/index.html
vendored
@@ -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">
|
||||||
|
|||||||
@@ -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.999(UTC时间)
|
||||||
|
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user