feat(ops): 添加自定义时间范围选择功能

功能特性:
- 在时间段选择器中增加"自定义"选项
- 点击后弹出对话框,支持选择任意时间范围
- 使用 HTML5 datetime-local 输入框,体验友好
- 自定义时显示格式化的时间范围标签(MM-DD HH:mm ~ MM-DD HH:mm)
- 默认初始化为最近1小时

技术实现:
- 扩展 TimeRange 类型支持 'custom'
- 添加 customStartTime 和 customEndTime 状态管理
- 创建 buildApiParams 辅助函数统一处理 API 参数
- 当选择自定义时,使用 start_time 和 end_time 参数替代 time_range
- 更新所有相关 API 调用支持自定义时间范围

国际化:
- 添加"自定义"、"开始时间"、"结束时间"翻译
This commit is contained in:
IanShaw027
2026-01-15 19:50:47 +08:00
parent 38961ba10e
commit 930e9ee55c
3 changed files with 135 additions and 71 deletions

View File

@@ -23,10 +23,13 @@
:auto-refresh-enabled="autoRefreshEnabled"
:auto-refresh-countdown="autoRefreshCountdown"
:fullscreen="isFullscreen"
:custom-start-time="customStartTime"
:custom-end-time="customEndTime"
@update:time-range="onTimeRangeChange"
@update:platform="onPlatformChange"
@update:group="onGroupChange"
@update:query-mode="onQueryModeChange"
@update:custom-time-range="onCustomTimeRangeChange"
@refresh="fetchData"
@open-request-details="handleOpenRequestDetails"
@open-error-details="openErrorDetails"
@@ -148,8 +151,8 @@ const { t } = useI18n()
const opsEnabled = computed(() => adminSettingsStore.opsMonitoringEnabled)
type TimeRange = '5m' | '30m' | '1h' | '6h' | '24h'
const allowedTimeRanges = new Set<TimeRange>(['5m', '30m', '1h', '6h', '24h'])
type TimeRange = '5m' | '30m' | '1h' | '6h' | '24h' | 'custom'
const allowedTimeRanges = new Set<TimeRange>(['5m', '30m', '1h', '6h', '24h', 'custom'])
type QueryMode = 'auto' | 'raw' | 'preagg'
const allowedQueryModes = new Set<QueryMode>(['auto', 'raw', 'preagg'])
@@ -163,6 +166,8 @@ const timeRange = ref<TimeRange>('1h')
const platform = ref<string>('')
const groupId = ref<number | null>(null)
const queryMode = ref<QueryMode>('auto')
const customStartTime = ref<string | null>(null)
const customEndTime = ref<string | null>(null)
const QUERY_KEYS = {
timeRange: 'tr',
@@ -420,6 +425,11 @@ function onTimeRangeChange(v: string | number | boolean | null) {
timeRange.value = v as TimeRange
}
function onCustomTimeRangeChange(startTime: string, endTime: string) {
customStartTime.value = startTime
customEndTime.value = endTime
}
function onSettingsSaved() {
loadThresholds()
fetchData()
@@ -458,18 +468,25 @@ function openError(id: number) {
showErrorModal.value = true
}
function buildApiParams() {
const params: any = {
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
}
if (timeRange.value === 'custom' && customStartTime.value && customEndTime.value) {
params.start_time = customStartTime.value
params.end_time = customEndTime.value
} else {
params.time_range = timeRange.value
}
return params
}
async function refreshOverviewWithCancel(fetchSeq: number, signal: AbortSignal) {
if (!opsEnabled.value) return
try {
const data = await opsAPI.getDashboardOverview(
{
time_range: timeRange.value,
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
},
{ signal }
)
const data = await opsAPI.getDashboardOverview(buildApiParams(), { signal })
if (fetchSeq !== dashboardFetchSeq) return
overview.value = data
} catch (err: any) {
@@ -483,15 +500,7 @@ async function refreshThroughputTrendWithCancel(fetchSeq: number, signal: AbortS
if (!opsEnabled.value) return
loadingTrend.value = true
try {
const data = await opsAPI.getThroughputTrend(
{
time_range: timeRange.value,
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
},
{ signal }
)
const data = await opsAPI.getThroughputTrend(buildApiParams(), { signal })
if (fetchSeq !== dashboardFetchSeq) return
throughputTrend.value = data
} catch (err: any) {
@@ -509,15 +518,7 @@ async function refreshLatencyHistogramWithCancel(fetchSeq: number, signal: Abort
if (!opsEnabled.value) return
loadingLatency.value = true
try {
const data = await opsAPI.getLatencyHistogram(
{
time_range: timeRange.value,
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
},
{ signal }
)
const data = await opsAPI.getLatencyHistogram(buildApiParams(), { signal })
if (fetchSeq !== dashboardFetchSeq) return
latencyHistogram.value = data
} catch (err: any) {
@@ -535,15 +536,7 @@ async function refreshErrorTrendWithCancel(fetchSeq: number, signal: AbortSignal
if (!opsEnabled.value) return
loadingErrorTrend.value = true
try {
const data = await opsAPI.getErrorTrend(
{
time_range: timeRange.value,
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
},
{ signal }
)
const data = await opsAPI.getErrorTrend(buildApiParams(), { signal })
if (fetchSeq !== dashboardFetchSeq) return
errorTrend.value = data
} catch (err: any) {
@@ -561,15 +554,7 @@ async function refreshErrorDistributionWithCancel(fetchSeq: number, signal: Abor
if (!opsEnabled.value) return
loadingErrorDistribution.value = true
try {
const data = await opsAPI.getErrorDistribution(
{
time_range: timeRange.value,
platform: platform.value || undefined,
group_id: groupId.value ?? undefined,
mode: queryMode.value
},
{ signal }
)
const data = await opsAPI.getErrorDistribution(buildApiParams(), { signal })
if (fetchSeq !== dashboardFetchSeq) return
errorDistribution.value = data
} catch (err: any) {