diff --git a/index.html b/index.html index 08b40cd..175c312 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ : window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; document.documentElement.dataset.theme = theme; document.documentElement.style.colorScheme = theme; - document.documentElement.style.backgroundColor = theme === 'dark' ? '#0b0d11' : '#f6f7f5'; + document.documentElement.style.backgroundColor = theme === 'dark' ? '#0b0d11' : '#ffffff'; })(); EasyCLIProxyAPI diff --git a/src/pages/UsageRecordsPage.tsx b/src/pages/UsageRecordsPage.tsx index fc00aef..e1bc8cf 100644 --- a/src/pages/UsageRecordsPage.tsx +++ b/src/pages/UsageRecordsPage.tsx @@ -6,7 +6,6 @@ import { Activity, Database, Gauge, - ListFilter, Pencil, RefreshCw, Trash2, @@ -248,7 +247,6 @@ export function UsageRecordsPage() { const [source, setSource] = useState(''); const [apiKeyHash, setApiKeyHash] = useState(''); const [result, setResult] = useState('all'); - const [advancedFiltersOpen, setAdvancedFiltersOpen] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [status, setStatus] = useState(null); @@ -447,7 +445,6 @@ export function UsageRecordsPage() { }; const collectorTone = status?.state === 'error' ? 'error' : status?.state === 'collecting' ? 'success' : ''; - const activeAdvancedFilterCount = [model, provider, source, apiKeyHash].filter(Boolean).length; const hasCurrentSnapshot = loadedScopeKey === scopeKey; const showInitialLoading = activeTab !== 'data-management' && @@ -471,135 +468,118 @@ export function UsageRecordsPage() { const filterPanel = (
-
- {t('usage.filter.title')} - {activeAdvancedFilterCount > 0 ? ( - {t('usage.filter.active', { count: activeAdvancedFilterCount })} - ) : null} -
-
+
+ value={range} + onChange={(event) => { + setRange(event.currentTarget.value as UsageRange); + setPage(1); + }} + aria-label={t('usage.filter.timeRange')} + > + + + + + + + + + - -
- + + + + + + + + +
+
- {advancedFiltersOpen ? ( -
- - - - -
- ) : null} - - {range === 'custom' ? ( -
- setCustomStart(event.currentTarget.value)} - aria-label={t('usage.filter.startTime')} - /> - {t('usage.filter.to')} - setCustomEnd(event.currentTarget.value)} - aria-label={t('usage.filter.endTime')} - /> -
- ) : null} -
+ {range === 'custom' ? ( +
+ setCustomStart(event.currentTarget.value)} + aria-label={t('usage.filter.startTime')} + /> + {t('usage.filter.to')} + setCustomEnd(event.currentTarget.value)} + aria-label={t('usage.filter.endTime')} + /> +
+ ) : null} + ); return ( diff --git a/src/styles/usage-subpages.css b/src/styles/usage-subpages.css index 0d4f182..4bd9884 100644 --- a/src/styles/usage-subpages.css +++ b/src/styles/usage-subpages.css @@ -98,104 +98,28 @@ background: var(--usage-page-hover); } -/* Usage records: keep the common path quiet, and move diagnostic filters out of the way. */ -.usage-records-page .usage-filter-panel { - gap: 8px; -} - -.usage-records-page .usage-filter-header { +/* Keep every usage filter visible. Each select sizes to its option text. */ +.usage-records-page .usage-filter-group { display: flex; + flex-wrap: wrap; align-items: center; - gap: 8px; - min-height: 22px; -} - -.usage-records-page .usage-filter-title { - color: var(--text-primary); - font-size: var(--font-size-caption); - font-weight: 650; -} - -.usage-records-page .usage-filter-active { - padding: 2px 7px; - border-radius: 999px; - background: color-mix(in srgb, var(--ui-accent) 10%, transparent); - color: var(--ui-accent-strong); - font-size: 11px; - font-weight: 600; -} - -.usage-records-page .usage-filter-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.usage-records-page .usage-filter-group.usage-filter-primary { - display: flex; - flex: 0 1 auto; - min-width: 0; - flex-wrap: nowrap; grid-template-columns: none; } -.usage-records-page .usage-filter-primary .usage-filter-item { - flex: 0 1 230px; - min-width: 170px; -} - -.usage-records-page .usage-filter-toggle { - display: inline-flex; +.usage-records-page .usage-filter-item { flex: 0 0 auto; - align-items: center; - justify-content: center; - gap: 6px; - height: 36px; - padding: 0 11px; - border: 1px solid var(--border-color); - border-radius: 10px; - background: var(--bg-primary); - color: var(--text-secondary); - font: inherit; - font-size: var(--font-size-caption); - font-weight: 600; - cursor: pointer; - transition: border-color .15s ease, background .15s ease, color .15s ease; -} - -.usage-records-page .usage-filter-toggle:hover, -.usage-records-page .usage-filter-toggle.is-open { - border-color: var(--ui-accent); - background: color-mix(in srgb, var(--ui-accent) 7%, var(--bg-primary)); - color: var(--ui-accent-strong); -} - -.usage-records-page .usage-filter-toggle b { - display: inline-grid; - min-width: 18px; - height: 18px; - place-items: center; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--ui-accent) 14%, transparent); - font-size: 10px; -} - -.usage-records-page .usage-filter-advanced { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); - gap: 8px; - padding-top: 8px; - border-top: 1px solid var(--border-color); -} - -.usage-records-page .usage-filter-advanced .usage-filter-item { + width: max-content; + max-width: 100%; min-width: 0; + padding: 0 10px 0 12px; } -.usage-records-page .usage-custom-range { - padding-top: 8px; +.usage-records-page .usage-filter-item select { + width: max-content; + max-width: 100%; + min-width: 0; + field-sizing: content; + padding: 0 20px 0 0; } /* The default event view is intentionally compact; secondary diagnostics remain available via tooltips and Columns. */ @@ -231,39 +155,3 @@ background: color-mix(in srgb, var(--usage-log-surface) 97%, var(--ui-accent)); } -@media (max-width: 760px) { - .usage-records-page .usage-filter-row { - align-items: stretch; - flex-direction: column; - } - - .usage-records-page .usage-filter-group.usage-filter-primary { - width: 100%; - flex-wrap: wrap; - } - - .usage-records-page .usage-filter-primary .usage-filter-item { - flex: 1 1 0; - min-width: 0; - } - - .usage-records-page .usage-filter-toggle { - width: 100%; - } - - .usage-records-page .usage-filter-advanced { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -@media (max-width: 480px) { - .usage-records-page .usage-filter-group.usage-filter-primary, - .usage-records-page .usage-filter-advanced { - grid-template-columns: 1fr; - flex-direction: column; - } - - .usage-records-page .usage-filter-advanced { - display: grid; - } -} diff --git a/src/styles/workspace-white.css b/src/styles/workspace-white.css index 3f82648..d80cbcc 100644 --- a/src/styles/workspace-white.css +++ b/src/styles/workspace-white.css @@ -1,4 +1,18 @@ /* Light-mode workspace surfaces. Keep the sidebar and semantic status colors separate. */ +:root:not([data-theme='dark']) { + --theme-faf9f5: #fff; + --bg-secondary: #fff; +} + +:root:not([data-theme='dark']), +:root:not([data-theme='dark']) body, +:root:not([data-theme='dark']) #root, +:root:not([data-theme='dark']) .app-shell, +:root:not([data-theme='dark']) .workspace, +:root:not([data-theme='dark']) .workspace .content { + background: #fff; +} + :root:not([data-theme='dark']) :is(.workspace, .config-dialog, dialog) { --bg-primary: #fff; --bg-secondary: #fff; @@ -27,11 +41,6 @@ --text-quaternary: #526174; } -:root:not([data-theme='dark']) .workspace, -:root:not([data-theme='dark']) .workspace .content { - background: #fff; -} - :root:not([data-theme='dark']) .workspace :is( .agent-model-section, .agent-auth-segmented, .routing-segmented, .plugin-tabs, .usage-analysis-metric, @@ -114,6 +123,64 @@ --usage-log-hover: #fff; } +/* The usage views and outer panels should read as clean white documents in light mode. + Remove neutral zebra fills, gray collector pills, and diffuse card shadows while + retaining borders and semantic colors for structure. */ +:root:not([data-theme='dark']) .workspace :is( + .usage-records-page, + .usage-tab-panel, + .usage-filter-panel, + .usage-request-log, + .usage-events-footer +) { + background: #fff; +} + +:root:not([data-theme='dark']) .workspace .usage-records-page :is( + .usage-trend-panel, + .usage-health-panel, + .usage-events-panel +) { + background: #fff; + border: 1px solid var(--border-color); + box-shadow: none; +} + +:root:not([data-theme='dark']) .workspace .usage-records-page :is( + .usage-filter-item, + .usage-filter-toggle +) { + background: #fff; + border-color: var(--border-color); +} + +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-request-log .usage-events-table tbody tr td, +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-request-log .usage-events-table tbody tr:nth-child(even) td, +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-request-log .usage-events-table tbody tr:hover td { + background: #fff !important; +} + +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-pricing-table :is(th, td) { + background: #fff; +} + +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-pricing-table tbody tr:hover td { + background: #fff !important; +} + +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-collector-state, +:root:not([data-theme='dark']) .workspace .usage-records-page + .usage-refresh-btn { + background: #fff; + border: 1px solid var(--border-color); +} + /* Empty tracks still need a visible outline after neutral surfaces become white. */ :root:not([data-theme='dark']) .workspace .usage-model-bar-track { box-shadow: inset 0 0 0 1px var(--border-color); diff --git a/tests/app-shell-accessibility-ui.cjs b/tests/app-shell-accessibility-ui.cjs index 14d6204..072a03a 100644 --- a/tests/app-shell-accessibility-ui.cjs +++ b/tests/app-shell-accessibility-ui.cjs @@ -36,6 +36,26 @@ const path = require('node:path'); await tabs.first().focus(); await page.keyboard.press('ArrowRight'); assert.equal(await tabs.nth(1).getAttribute('aria-selected'), 'true'); + await page.getByRole('tab', { name: /请求明细/ }).click(); + await page.locator('.usage-events-table tbody tr').first().waitFor(); + const usageSurfaces = await page.locator('.usage-records-page .usage-events-table tbody tr').first().evaluate(row => { + const cell = row.querySelector('td'); + const page = row.closest('.usage-records-page'); + const status = page?.querySelector('.usage-collector-state'); + return { row: cell ? getComputedStyle(cell).backgroundColor : '', status: status ? getComputedStyle(status).backgroundColor : '' }; + }); + assert.equal(usageSurfaces.row, 'rgb(255, 255, 255)', 'light usage rows stay pure white'); + assert.equal(usageSurfaces.status, 'rgb(255, 255, 255)', 'light usage status stays pure white'); + const shellSurfaces = await page.evaluate(() => { + const shell = document.querySelector('.app-shell'); + const content = document.querySelector('.content'); + return { + shell: shell ? getComputedStyle(shell).backgroundColor : '', + content: content ? getComputedStyle(content).backgroundColor : '', + }; + }); + assert.equal(shellSurfaces.shell, 'rgb(255, 255, 255)', 'light app shell stays pure white'); + assert.equal(shellSurfaces.content, 'rgb(255, 255, 255)', 'light content stays pure white'); await page.getByRole('tab', { name: /数据管理/ }).click(); assert.equal(await page.locator('.usage-filter-panel').count(), 0);