diff --git a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
index 63f4b912..c0eb64da 100644
--- a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.test.tsx
@@ -88,35 +88,20 @@ test('treats login and logout as complete query and mutation cache boundaries',
expect(window.sessionStorage.length).toBe(0);
});
-test('makes account and operations-token entry explicit while preserving the account draft', async () => {
+test('exposes only account and password login on the public sign-in screen', async () => {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
- mocks.session.mockImplementation(async () => {
- if (getAccessToken() !== 'ops-session-token') throw new Error('未登录');
- return { name: 'platform-operator', role: 'admin', authMode: 'enforce' };
- });
+ mocks.session.mockRejectedValue(new Error('未登录'));
render( undefined} />);
expect(await screen.findByRole('form', { name: '平台账号登录' })).toBeInTheDocument();
- expect(screen.getByRole('tab', { name: '账号登录' })).toHaveAttribute('aria-selected', 'true');
- expect(screen.getByRole('tab', { name: '运维令牌' })).toHaveAttribute('aria-selected', 'false');
- fireEvent.change(screen.getByPlaceholderText('请输入用户名'), { target: { value: 'remember-me' } });
-
- fireEvent.click(screen.getByRole('tab', { name: '运维令牌' }));
- expect(screen.getByRole('form', { name: '运维令牌登录' })).toBeInTheDocument();
- expect(screen.getByText('仅限平台运维')).toBeInTheDocument();
- expect(screen.getByText('令牌只保存在当前浏览器会话,关闭或退出后失效。')).toBeInTheDocument();
- expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument();
- fireEvent.change(screen.getByPlaceholderText('请输入服务器访问令牌'), { target: { value: 'ops-session-token' } });
-
- fireEvent.click(screen.getByRole('tab', { name: '账号登录' }));
- expect(screen.getByPlaceholderText('请输入用户名')).toHaveValue('remember-me');
- fireEvent.click(screen.getByRole('tab', { name: '运维令牌' }));
- fireEvent.click(screen.getByRole('button', { name: '验证令牌并进入' }));
-
- expect(await screen.findByText('platform-operator')).toBeInTheDocument();
- expect(mocks.login).not.toHaveBeenCalled();
- expect(getAccessToken()).toBe('ops-session-token');
+ expect(screen.getByPlaceholderText('请输入用户名')).toHaveAttribute('autocomplete', 'username');
+ expect(screen.getByPlaceholderText('请输入密码')).toHaveAttribute('autocomplete', 'current-password');
+ expect(screen.queryByRole('tab')).not.toBeInTheDocument();
+ expect(screen.queryByText('运维令牌')).not.toBeInTheDocument();
+ expect(screen.queryByPlaceholderText('请输入服务器访问令牌')).not.toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: '验证令牌并进入' })).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '登录工作台' })).toBeDisabled();
});
test('an expired protected request returns to login and removes the active user cache', async () => {
diff --git a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
index d9459503..168f6bca 100644
--- a/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/auth/AuthGate.tsx
@@ -1,6 +1,6 @@
-import { IconAlertTriangle, IconKey, IconLock, IconSafe, IconShield, IconUser } from '@douyinfe/semi-icons';
+import { IconAlertTriangle, IconLock, IconSafe, IconShield, IconUser } from '@douyinfe/semi-icons';
import { useQuery, useQueryClient } from '@tanstack/react-query';
-import { Banner, Button, Card, Input, Spin, Tabs, Tag, Typography } from '@douyinfe/semi-ui';
+import { Banner, Button, Card, Input, Spin, Tag, Typography } from '@douyinfe/semi-ui';
import { FormEvent, ReactNode, useCallback, useContext, useEffect, useState, createContext } from 'react';
import { api } from '../../api/client';
import { PasswordInput } from '../shared/PasswordInput';
@@ -79,8 +79,6 @@ export function AuthGate({ children }: { children: ReactNode }) {
const [tokenVersion, setTokenVersion] = useState(0);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
- const [draftToken, setDraftToken] = useState('');
- const [legacyMode, setLegacyMode] = useState(false);
const [attempted, setAttempted] = useState(() => Boolean(getAccessToken()));
const [loginPending, setLoginPending] = useState(false);
const [loginError, setLoginError] = useState('');
@@ -98,7 +96,6 @@ export function AuthGate({ children }: { children: ReactNode }) {
clearAccessToken();
clearClientSession();
setPassword('');
- setDraftToken('');
setAttempted(false);
setLoginError('');
setTokenVersion((value) => value + 1);
@@ -117,12 +114,8 @@ export function AuthGate({ children }: { children: ReactNode }) {
setLoginError('');
setLoginPending(true);
try {
- if (legacyMode) {
- setAccessToken(draftToken);
- } else {
- const result = await api.login({ username: username.trim(), password });
- setAccessToken(result.accessToken);
- }
+ const result = await api.login({ username: username.trim(), password });
+ setAccessToken(result.accessToken);
clearClientSession();
setTokenVersion((value) => value + 1);
} catch (error) {
@@ -132,11 +125,6 @@ export function AuthGate({ children }: { children: ReactNode }) {
setLoginPending(false);
}
};
- const switchLoginMode = (mode: string) => {
- setLegacyMode(mode === 'token');
- setLoginError('');
- };
-
if (session.isPending) {
return ;
}
@@ -159,45 +147,20 @@ export function AuthGate({ children }: { children: ReactNode }) {
-
diff --git a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx
index cc002826..c7f47883 100644
--- a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx
@@ -118,15 +118,19 @@ test('starts without a selection and supports expand, collapse, reselection, and
expect(firstVehicle).toHaveClass('semi-button', 'v2-vehicle-row');
expect(view.container.querySelector('.v2-filterbar')).toHaveClass('semi-card');
- const kpis = view.container.querySelector('.v2-kpis');
- expect(kpis).toHaveClass('semi-card');
- expect(Array.from(kpis?.querySelectorAll('.v2-kpi small') ?? []).map((item) => item.textContent)).toEqual([
- '车辆总数', '当前在线', '当前离线', '行驶车辆', '静止车辆', '告警车辆', '今日上报', '无实时位置'
+ const kpis = screen.getByRole('list', { name: '车辆整体统计' });
+ expect(kpis.closest('.v2-monitor-summary-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
+ expect(Array.from(kpis.querySelectorAll(':scope > [role="listitem"] > small')).map((item) => item.textContent)).toEqual([
+ '车辆总数', '当前在线', '行驶车辆', '当前离线', '静止车辆', '告警车辆'
]);
- expect(kpis?.querySelector('.v2-kpi.is-fleet')).toHaveClass('is-primary');
- expect(kpis?.querySelector('.v2-kpi.is-online')).toHaveClass('is-primary');
- expect(kpis?.querySelector('.v2-kpi.is-today')).toHaveClass('is-support');
- expect(kpis?.querySelector('.v2-kpi:last-child')).toHaveClass('is-missing', 'is-support');
+ expect(kpis.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(6);
+ expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(1)')).toHaveClass('is-primary');
+ expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(2)')).toHaveClass('is-primary', 'is-success');
+ expect(kpis.querySelector(':scope > [role="listitem"]:nth-child(4)')).toHaveClass('is-secondary');
+ const summarySupport = view.container.querySelector('.v2-monitor-summary-support');
+ expect(summarySupport).toHaveTextContent('今日上报10条数据活跃度');
+ expect(summarySupport).toHaveTextContent('无实时位置0辆辅助排查');
+ expect(view.container.querySelector('.v2-kpis')).not.toBeInTheDocument();
expect(view.container.querySelector('.v2-event-strip')).toHaveClass('semi-card');
expect(view.container.querySelector('.v2-monitor-live-tag')).toHaveClass('semi-tag');
const liveStatus = screen.getByLabelText('实时数据状态');
diff --git a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx
index 9151bf8f..dc05c5d5 100644
--- a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx
@@ -13,6 +13,7 @@ import { EmptyState, InlineError } from '../shared/AsyncState';
import { SegmentedTabs } from '../shared/SegmentedTabs';
import { TablePagination } from '../shared/TablePagination';
import { ProtocolTag } from '../shared/ProtocolTag';
+import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
import { formatNumber, statusLabel, vehicleStatus } from '../domain/monitor';
@@ -340,6 +341,56 @@ export default function MonitorPage() {
const driving = rows.filter((vehicle) => vehicleStatus(vehicle) === 'driving').length;
const idle = rows.filter((vehicle) => vehicleStatus(vehicle) === 'idle').length;
const offline = rows.filter((vehicle) => vehicleStatus(vehicle) === 'offline').length;
+ const totalVehicleCount = summary.data?.totalVehicles ?? vehicles.data?.total ?? rows.length;
+ const onlineVehicleCount = summary.data?.onlineVehicles ?? rows.length - offline;
+ const offlineVehicleCount = summary.data?.offlineVehicles ?? offline;
+ const drivingVehicleCount = summary.data?.drivingVehicles ?? driving;
+ const idleVehicleCount = summary.data?.idleVehicles ?? idle;
+ const alertVehicleCount = summary.data?.alertDataAvailable ? summary.data.alertVehicles : undefined;
+ const monitorMetrics: WorkspaceQueueMetricRailItem[] = [
+ {
+ label: '车辆总数',
+ value: <>{formatNumber(totalVehicleCount)}辆>,
+ note: '授权车辆',
+ tone: 'primary',
+ emphasis: 'primary'
+ },
+ {
+ label: '当前在线',
+ value: <>{formatNumber(onlineVehicleCount)}辆>,
+ note: '当前可用',
+ tone: 'success',
+ emphasis: 'primary'
+ },
+ {
+ label: '行驶车辆',
+ value: <>{formatNumber(drivingVehicleCount)}辆>,
+ note: '实时行驶',
+ tone: 'primary',
+ emphasis: 'primary'
+ },
+ {
+ label: '当前离线',
+ value: <>{formatNumber(offlineVehicleCount)}辆>,
+ note: '无在线来源',
+ tone: 'neutral',
+ emphasis: 'secondary'
+ },
+ {
+ label: '静止车辆',
+ value: <>{formatNumber(idleVehicleCount)}辆>,
+ note: '在线静止',
+ tone: 'success',
+ emphasis: 'secondary'
+ },
+ {
+ label: '告警车辆',
+ value: <>{alertVehicleCount == null ? '—' : formatNumber(alertVehicleCount)}辆>,
+ note: alertVehicleCount == null ? '数据未开放' : '需要关注',
+ tone: alertVehicleCount ? 'danger' : 'neutral',
+ emphasis: 'primary'
+ }
+ ];
const viewportLoad = mode === 'map'
? (map.data?.clusters.length
? `${map.data.clusters.length} 个聚合 · ${map.data.points.length} 个车辆点`
@@ -409,18 +460,16 @@ export default function MonitorPage() {
{batchSearchOpen ? setBatchSearchOpen(false)} onApply={(value) => { setKeyword(value); setListOffset(0); setBatchSearchOpen(false); }} /> : null}
-
- {[
- { label: '车辆总数', value: formatNumber(summary.data?.totalVehicles ?? vehicles.data?.total ?? rows.length), tone: 'fleet', priority: 'primary', unit: '辆' },
- { label: '当前在线', value: formatNumber(summary.data?.onlineVehicles ?? rows.length - offline), tone: 'online', priority: 'primary', unit: '辆' },
- { label: '当前离线', value: formatNumber(summary.data?.offlineVehicles ?? offline), tone: 'offline', priority: 'standard', unit: '辆' },
- { label: '行驶车辆', value: formatNumber(summary.data?.drivingVehicles ?? driving), tone: 'driving', priority: 'standard', unit: '辆' },
- { label: '静止车辆', value: formatNumber(summary.data?.idleVehicles ?? idle), tone: 'idle', priority: 'standard', unit: '辆' },
- { label: '告警车辆', value: summary.data?.alertDataAvailable ? formatNumber(summary.data.alertVehicles) : '—', tone: 'alert', priority: 'standard', unit: '辆' },
- { label: '今日上报', value: formatNumber(summary.data?.frameToday ?? 0), tone: 'today', priority: 'support', unit: '条' },
- { label: '无实时位置', value: formatNumber(summary.data?.noLocationVehicles ?? 0), tone: 'missing', priority: 'support', unit: '辆' }
- ].map(({ label, value, tone, priority, unit }) => {label}{value}{unit}
)}
-
+
+ 今日上报{formatNumber(summary.data?.frameToday ?? 0)}条数据活跃度
+ 无实时位置{formatNumber(summary.data?.noLocationVehicles ?? 0)}辆辅助排查
+ }
+ />
{vehicles.isError ? vehicles.refetch()} /> : null}
{mode === 'list' && realtimeListQuery.isError ? realtimeListQuery.refetch()} /> : null}
diff --git a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts
index 47efe9cd..2f5cc343 100644
--- a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts
+++ b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts
@@ -513,7 +513,10 @@ describe('V2 production entry', () => {
expect(corePageSources.MonitorPage).not.toContain('