From b1fe595f0b2dabea108d61adc7e9828d0a04731c Mon Sep 17 00:00:00 2001
From: lzt404 <2596933790@qq.com>
Date: Mon, 5 Oct 2026 15:42:04 +0800
Subject: [PATCH] style(ui): unify light mode with pure white background across
app shell and usage views
- Set default light background color to pure white across html, body, and app-shell
- Replace grayish background, diffuse card shadows, and translucent pills in usage records with pure white surfaces and clean borders
- Ensure usage tables and subpage panels render consistently as pure white documents
- Update UI accessibility regression tests to assert pure white surfaces
---
index.html | 2 +-
src/pages/UsageRecordsPage.tsx | 232 ++++++++++++---------------
src/styles/usage-subpages.css | 138 ++--------------
src/styles/workspace-white.css | 77 ++++++++-
tests/app-shell-accessibility-ui.cjs | 20 +++
5 files changed, 212 insertions(+), 257 deletions(-)
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);