fix ops dashboard mobile overflow

This commit is contained in:
cyber-art
2026-07-22 03:11:19 +08:00
parent 5a8d6c4e41
commit a8bfdc6f36
3 changed files with 70 additions and 14 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
<template>
<header class="glass sticky top-0 z-30 border-b border-gray-200/50 dark:border-dark-700/50">
<div class="flex h-16 items-center justify-between px-4 md:px-6">
<div class="flex h-16 items-center justify-between gap-2 px-2 sm:px-4 md:px-6">
<!-- Left: Mobile Menu Toggle + Page Title -->
<div class="flex items-center gap-4">
<div class="flex shrink-0 items-center gap-2 sm:gap-4">
<button
@click="toggleMobileSidebar"
class="btn-ghost btn-icon lg:hidden"
@@ -22,7 +22,7 @@
</div>
<!-- Right: Announcements + Docs + Language + Subscriptions + Balance + User Dropdown -->
<div class="flex items-center gap-3">
<div class="flex min-w-0 items-center gap-1 sm:gap-3">
<!-- Announcement Bell -->
<AnnouncementBell v-if="user" />
@@ -32,7 +32,7 @@
:href="docUrl"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-dark-400 dark:hover:bg-dark-800 dark:hover:text-white"
class="hidden items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-dark-400 dark:hover:bg-dark-800 dark:hover:text-white sm:flex"
>
<Icon name="book" size="sm" />
<span class="hidden sm:inline">{{ t('nav.docs') }}</span>
@@ -173,22 +173,28 @@ function downloadChart() {
</script>
<template>
<div class="flex h-full flex-col rounded-3xl bg-white p-6 shadow-sm ring-1 ring-gray-900/5 dark:bg-dark-800 dark:ring-dark-700">
<div class="mb-4 flex shrink-0 items-center justify-between">
<h3 class="flex items-center gap-2 text-sm font-bold text-gray-900 dark:text-white">
<div class="flex h-full min-w-0 flex-col rounded-3xl bg-white p-6 shadow-sm ring-1 ring-gray-900/5 dark:bg-dark-800 dark:ring-dark-700">
<div
data-testid="throughput-chart-header"
class="mb-4 flex shrink-0 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
>
<h3 class="flex min-w-0 items-center gap-2 text-sm font-bold text-gray-900 dark:text-white">
<svg class="h-4 w-4 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
{{ t('admin.ops.throughputTrend') }}
<HelpTooltip v-if="!props.fullscreen" :content="t('admin.ops.tooltips.throughputTrend')" />
</h3>
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<span class="flex items-center gap-1"><span class="h-2 w-2 rounded-full bg-blue-500"></span>QPS</span>
<span class="flex items-center gap-1"><span class="h-2 w-2 rounded-full bg-green-500"></span>{{ t('admin.ops.tpsK') }}</span>
<div
data-testid="throughput-chart-toolbar"
class="flex w-full min-w-0 flex-wrap items-center gap-2 text-xs text-gray-500 dark:text-gray-400 sm:w-auto sm:justify-end"
>
<span class="flex shrink-0 items-center gap-1"><span class="h-2 w-2 rounded-full bg-blue-500"></span>QPS</span>
<span class="flex shrink-0 items-center gap-1"><span class="h-2 w-2 rounded-full bg-green-500"></span>{{ t('admin.ops.tpsK') }}</span>
<template v-if="!props.fullscreen">
<button
type="button"
class="ml-2 inline-flex items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
class="inline-flex shrink-0 items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
:disabled="state !== 'ready'"
:title="t('admin.ops.requestDetails.title')"
@click="emit('openDetails')"
@@ -197,7 +203,7 @@ function downloadChart() {
</button>
<button
type="button"
class="ml-2 inline-flex items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
class="inline-flex shrink-0 items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
:disabled="state !== 'ready'"
:title="t('admin.ops.charts.resetZoomHint')"
@click="resetZoom"
@@ -206,7 +212,7 @@ function downloadChart() {
</button>
<button
type="button"
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
class="inline-flex shrink-0 items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
:disabled="state !== 'ready'"
:title="t('admin.ops.charts.downloadChartHint')"
@click="downloadChart"
@@ -244,7 +250,7 @@ function downloadChart() {
</button>
</div>
<div class="min-h-0 flex-1">
<div class="min-h-0 min-w-0 flex-1">
<Line v-if="state === 'ready' && chartData" ref="throughputChartRef" :data="chartData" :options="options" />
<div v-else class="flex h-full items-center justify-center">
<div v-if="state === 'loading'" class="animate-pulse text-sm text-gray-400">{{ t('common.loading') }}</div>
@@ -0,0 +1,50 @@
import { describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import OpsThroughputTrendChart from '../OpsThroughputTrendChart.vue'
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({
t: (key: string) => key,
}),
}
})
vi.mock('vue-chartjs', () => ({
Line: {
props: ['data', 'options'],
template: '<div class="line-chart" />',
},
}))
describe('OpsThroughputTrendChart', () => {
it('allows the header controls to wrap on narrow screens', () => {
const wrapper = mount(OpsThroughputTrendChart, {
props: {
points: [],
loading: false,
timeRange: '1h',
},
global: {
stubs: {
EmptyState: true,
HelpTooltip: true,
},
},
})
const header = wrapper.get('[data-testid="throughput-chart-header"]')
expect(header.classes()).toEqual(expect.arrayContaining(['flex-col', 'sm:flex-row']))
const toolbar = wrapper.get('[data-testid="throughput-chart-toolbar"]')
expect(toolbar.classes()).toEqual(expect.arrayContaining(['w-full', 'flex-wrap', 'sm:w-auto']))
expect(toolbar.findAll('button')).toHaveLength(3)
toolbar.findAll('button').forEach((button) => {
expect(button.classes()).toContain('shrink-0')
expect(button.classes()).not.toContain('ml-2')
})
})
})