refine Semi UI alert and access workspaces

This commit is contained in:
lingniu
2026-07-18 04:26:37 +08:00
parent cff2e07a57
commit 866dfdaf5f
5 changed files with 375 additions and 36 deletions

View File

@@ -132,11 +132,16 @@ test('renders mobile access vehicles as selectable Semi cards', async () => {
prepareBaseData(); prepareBaseData();
mocks.accessVehicles.mockResolvedValue({ items: [accessRow('VIN001', '粤A00001')], total: 1, limit: 50, offset: 0 }); mocks.accessVehicles.mockResolvedValue({ items: [accessRow('VIN001', '粤A00001')], total: 1, limit: 50, offset: 0 });
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>); const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);
const action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' }); const action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' });
expect(action).toHaveClass('semi-button', 'v2-access-mobile-action'); expect(action).toHaveClass('semi-button', 'v2-access-mobile-action');
expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card'); expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card');
expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('aria-label', '车辆接入列表,可上下滚动');
expect(within(action).getByText('GB32960')).toBeInTheDocument();
expect(within(action).getByText('JT808')).toBeInTheDocument();
expect(within(action).getByText('YUTONG_MQTT')).toBeInTheDocument();
expect(action).toHaveAttribute('aria-pressed', 'false'); expect(action).toHaveAttribute('aria-pressed', 'false');
expect(action).toHaveAttribute('aria-expanded', 'false'); expect(action).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(action); fireEvent.click(action);

View File

@@ -40,7 +40,7 @@ function compactTime(value: string) {
return compactAccessTimeFormatter.format(parsed).replace(/\//g, '-'); return compactAccessTimeFormatter.format(parsed).replace(/\//g, '-');
} }
function ProtocolState({ status, detailed = false }: { status?: AccessProtocolStatus; detailed?: boolean }) { function ProtocolState({ status, detailed = false, protocolLabel }: { status?: AccessProtocolStatus; detailed?: boolean; protocolLabel?: string }) {
const state = !status?.connected ? 'missing' : status.onlineState; const state = !status?.connected ? 'missing' : status.onlineState;
const label = state === 'missing' ? '无来源' : state === 'online' ? '在线' : state === 'offline' ? '离线' : state === 'unknown' ? '未知' : '从未上报'; const label = state === 'missing' ? '无来源' : state === 'online' ? '在线' : state === 'offline' ? '离线' : state === 'unknown' ? '未知' : '从未上报';
const color = state === 'online' ? 'green' : state === 'offline' || state === 'unknown' ? 'orange' : 'grey'; const color = state === 'online' ? 'green' : state === 'offline' || state === 'unknown' ? 'orange' : 'grey';
@@ -66,6 +66,7 @@ function ProtocolState({ status, detailed = false }: { status?: AccessProtocolSt
</Card>; </Card>;
} }
return <div className={`v2-access-protocol-cell is-${state}`} title={status?.latestReceivedAt ? `最新上报:${formatAccessTime(status.latestReceivedAt)}` : '当前未发现该协议来源'}> return <div className={`v2-access-protocol-cell is-${state}`} title={status?.latestReceivedAt ? `最新上报:${formatAccessTime(status.latestReceivedAt)}` : '当前未发现该协议来源'}>
{protocolLabel ? <b className="v2-access-protocol-label">{protocolLabel}</b> : null}
<span><i />{label}</span> <span><i />{label}</span>
<strong>{status?.connected ? compactTime(status.latestReceivedAt) : '—'}</strong> <strong>{status?.connected ? compactTime(status.latestReceivedAt) : '—'}</strong>
<small>{status?.provider || (status?.connected ? '接入方未维护' : '未发现来源')}</small> <small>{status?.provider || (status?.connected ? '接入方未维护' : '未发现来源')}</small>
@@ -246,9 +247,13 @@ export default function AccessPage() {
actionsClassName="v2-access-table-actions" actionsClassName="v2-access-table-actions"
actions={<><ProtocolCoverage summary={summary} /><Button theme="light" icon={<IconDownload />} onClick={() => downloadRows(rows)} disabled={!rows.length}></Button></>} actions={<><ProtocolCoverage summary={summary} /><Button theme="light" icon={<IconDownload />} onClick={() => downloadRows(rows)} disabled={!rows.length}></Button></>}
/> />
<div className="v2-access-table-scroll-v3"> <div
className={`v2-access-table-scroll-v3${mobileLayout ? ' is-mobile-scroll' : ''}`}
tabIndex={mobileLayout ? 0 : undefined}
aria-label={mobileLayout ? '车辆接入列表,可上下滚动' : undefined}
>
{mobileLayout {mobileLayout
? <div className="v2-access-mobile-list">{rows.map((row) => <Card key={row.vin} className={`v2-access-mobile-card${selected?.vin === row.vin ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" aria-pressed={selected?.vin === row.vin} aria-expanded={selected?.vin === row.vin} aria-label={`查看 ${row.plate || row.vin} 接入详情`} className="v2-access-mobile-action" onClick={() => setSelectedVIN(row.vin)}><span className="v2-access-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><ConnectionState row={row} /></header><p>{row.oem || '品牌未维护'} · {row.model || row.company || '车型未维护'}</p><span className="v2-access-mobile-protocols">{PROTOCOLS.map((protocol) => <ProtocolState key={protocol} status={statusByProtocol(row, protocol)} />)}</span><footer><IconChevronRight /></footer></span></Button></Card>)}</div> ? <div className="v2-access-mobile-list">{rows.map((row) => <Card key={row.vin} className={`v2-access-mobile-card${selected?.vin === row.vin ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" aria-pressed={selected?.vin === row.vin} aria-expanded={selected?.vin === row.vin} aria-label={`查看 ${row.plate || row.vin} 接入详情`} className="v2-access-mobile-action" onClick={() => setSelectedVIN(row.vin)}><span className="v2-access-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><ConnectionState row={row} /></header><p>{row.oem || '品牌未维护'} · {row.model || row.company || '车型未维护'}</p><span className="v2-access-mobile-protocols">{PROTOCOLS.map((protocol) => <ProtocolState key={protocol} protocolLabel={protocol} status={statusByProtocol(row, protocol)} />)}</span><footer><IconChevronRight /></footer></span></Button></Card>)}</div>
: <AccessVehicleTable rows={rows} selectedVIN={selectedVIN} onSelect={setSelectedVIN} />} : <AccessVehicleTable rows={rows} selectedVIN={selectedVIN} onSelect={setSelectedVIN} />}
{vehiclesQuery.isFetching ? <PanelLoading className="v2-access-loading" title="正在更新车辆接入状态…" description="当前列表返回后会自动替换。" compact={Boolean(rows.length)} /> : null} {vehiclesQuery.isFetching ? <PanelLoading className="v2-access-loading" title="正在更新车辆接入状态…" description="当前列表返回后会自动替换。" compact={Boolean(rows.length)} /> : null}
{!vehiclesQuery.isFetching && !rows.length ? <PanelEmpty className="v2-access-empty" title="没有匹配车辆" description="调整车牌、协议或接入状态筛选后重试。" /> : null} {!vehiclesQuery.isFetching && !rows.length ? <PanelEmpty className="v2-access-empty" title="没有匹配车辆" description="调整车牌、协议或接入状态筛选后重试。" /> : null}

View File

@@ -188,6 +188,8 @@ test('renders only selectable Semi alert cards on mobile', async () => {
const action = await screen.findByRole('button', { name: '查看 粤A移动01 粤A移动01速度告警 告警详情' }); const action = await screen.findByRole('button', { name: '查看 粤A移动01 粤A移动01速度告警 告警详情' });
expect(action).toHaveClass('semi-button', 'v2-alert-mobile-action'); expect(action).toHaveClass('semi-button', 'v2-alert-mobile-action');
expect(action.closest('.semi-card')).toHaveClass('v2-alert-mobile-card'); expect(action.closest('.semi-card')).toHaveClass('v2-alert-mobile-card');
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '告警事件列表,可上下滚动');
expect(view.container.querySelector('.v2-alert-event-table')).not.toBeInTheDocument(); expect(view.container.querySelector('.v2-alert-event-table')).not.toBeInTheDocument();
expect(action).toHaveAttribute('aria-expanded', 'false'); expect(action).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(action); fireEvent.click(action);

View File

@@ -223,7 +223,11 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
meta={`${(events.data?.total ?? 0).toLocaleString('zh-CN')}`} meta={`${(events.data?.total ?? 0).toLocaleString('zh-CN')}`}
actions={<Button theme="borderless" icon={<IconRefresh />} loading={events.isFetching} onClick={() => Promise.all([events.refetch(), summary.refetch(), ...(selectedID ? [detail.refetch()] : [])])}></Button>} actions={<Button theme="borderless" icon={<IconRefresh />} loading={events.isFetching} onClick={() => Promise.all([events.refetch(), summary.refetch(), ...(selectedID ? [detail.refetch()] : [])])}></Button>}
/> />
<div className="v2-alert-table-scroll"> <div
className={`v2-alert-table-scroll${mobileLayout ? ' is-mobile-scroll' : ''}`}
tabIndex={mobileLayout ? 0 : undefined}
aria-label={mobileLayout ? '告警事件列表,可上下滚动' : undefined}
>
{mobileLayout {mobileLayout
? <div className="v2-alert-mobile-list">{rows.map((event) => <Card key={event.id} className={`v2-alert-mobile-card${selectedID === event.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-alert-mobile-action" aria-pressed={selectedID === event.id} aria-expanded={selectedID === event.id} aria-label={`查看 ${event.plate || event.vin} ${event.ruleName} 告警详情`} onClick={() => setSelection({ scope: eventScope, id: event.id })}><span className="v2-alert-mobile-card-content"><header><strong>{event.plate || '未绑定车牌'}</strong><span><SeverityTag severity={event.severity} /><StatusTag status={event.status} /></span></header><p>{event.ruleName}</p><dl><div><dt></dt><dd>{formatAlertTime(event.triggeredAt)}</dd></div><div><dt></dt><dd>{event.protocol || '—'}</dd></div><div><dt></dt><dd>{alertValue(event)}</dd></div><div><dt></dt><dd>{thresholdText(event)}</dd></div></dl><footer><IconChevronRight /></footer></span></Button></Card>)}</div> ? <div className="v2-alert-mobile-list">{rows.map((event) => <Card key={event.id} className={`v2-alert-mobile-card${selectedID === event.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-alert-mobile-action" aria-pressed={selectedID === event.id} aria-expanded={selectedID === event.id} aria-label={`查看 ${event.plate || event.vin} ${event.ruleName} 告警详情`} onClick={() => setSelection({ scope: eventScope, id: event.id })}><span className="v2-alert-mobile-card-content"><header><strong>{event.plate || '未绑定车牌'}</strong><span><SeverityTag severity={event.severity} /><StatusTag status={event.status} /></span></header><p>{event.ruleName}</p><dl><div><dt></dt><dd>{formatAlertTime(event.triggeredAt)}</dd></div><div><dt></dt><dd>{event.protocol || '—'}</dd></div><div><dt></dt><dd>{alertValue(event)}</dd></div><div><dt></dt><dd>{thresholdText(event)}</dd></div></dl><footer><IconChevronRight /></footer></span></Button></Card>)}</div>
: <AlertEventTable rows={rows} selectedID={selectedID} onSelect={(id) => setSelection({ scope: eventScope, id })} />} : <AlertEventTable rows={rows} selectedID={selectedID} onSelect={(id) => setSelection({ scope: eventScope, id })} />}
@@ -410,5 +414,5 @@ export default function AlertsPage() {
const setTab = (next: Tab) => { setTabState(next); const copy = new URLSearchParams(params); copy.set('tab', next); setParams(copy, { replace: true }); }; const setTab = (next: Tab) => { setTabState(next); const copy = new URLSearchParams(params); copy.set('tab', next); setParams(copy, { replace: true }); };
const setFilters = (next: Filters) => { setFilterState(next); const copy = new URLSearchParams(); if (tab !== 'events') copy.set('tab', tab); Object.entries(next).forEach(([key, value]) => { if (value) copy.set(key, value); }); setParams(copy, { replace: true }); }; const setFilters = (next: Filters) => { setFilterState(next); const copy = new URLSearchParams(); if (tab !== 'events') copy.set('tab', tab); Object.entries(next).forEach(([key, value]) => { if (value) copy.set(key, value); }); setParams(copy, { replace: true }); };
const tabs = [{ key: 'events' as const, label: '告警事件', icon: <IconAlarm /> }, ...(admin ? [{ key: 'rules' as const, label: '规则配置' }] : []), { key: 'notifications' as const, label: '站内通知', icon: <IconBell />, count: unread || undefined }]; const tabs = [{ key: 'events' as const, label: '告警事件', icon: <IconAlarm /> }, ...(admin ? [{ key: 'rules' as const, label: '规则配置' }] : []), { key: 'notifications' as const, label: '站内通知', icon: <IconBell />, count: unread || undefined }];
return <div className="v2-alert-page"><div className="v2-alert-navigation"><SegmentedTabs className="v2-alert-tabs" variant="filled" ariaLabel="告警中心分类" value={activeTab} items={tabs} onChange={setTab} /><div className="v2-alert-navigation-meta"><Typography.Text type="tertiary">{operator ? '可处置事件' : '只读查看'}</Typography.Text><Tag color={unread ? 'orange' : 'green'} type="light" size="small">{unread ? `${unread.toLocaleString('zh-CN')} 条未读` : '通知已读'}</Tag></div></div>{activeTab !== 'notifications' && rules.isError ? <InlineError message={rules.error.message} onRetry={() => rules.refetch()} /> : null}{activeTab === 'rules' && metrics.isError ? <InlineError message={metrics.error.message} onRetry={() => metrics.refetch()} /> : null}{activeTab === 'events' ? <EventWorkspace filters={filters} draft={eventDraft} setDraft={setEventDraft} setFilters={setFilters} rules={rules.data ?? []} unread={unread} editable={operator} onTab={setTab} /> : activeTab === 'rules' ? <RulesWorkspace rules={rules.data ?? []} metrics={metrics.data?.metrics ?? []} /> : <NotificationsWorkspace editable={operator} notifications={notifications} />}</div>; return <div className={`v2-alert-page is-${activeTab}`}><div className="v2-alert-navigation"><SegmentedTabs className="v2-alert-tabs" variant="filled" ariaLabel="告警中心分类" value={activeTab} items={tabs} onChange={setTab} /><div className="v2-alert-navigation-meta"><Typography.Text type="tertiary">{operator ? '可处置事件' : '只读查看'}</Typography.Text><Tag color={unread ? 'orange' : 'green'} type="light" size="small">{unread ? `${unread.toLocaleString('zh-CN')} 条未读` : '通知已读'}</Tag></div></div>{activeTab !== 'notifications' && rules.isError ? <InlineError message={rules.error.message} onRetry={() => rules.refetch()} /> : null}{activeTab === 'rules' && metrics.isError ? <InlineError message={metrics.error.message} onRetry={() => metrics.refetch()} /> : null}{activeTab === 'events' ? <EventWorkspace filters={filters} draft={eventDraft} setDraft={setEventDraft} setFilters={setFilters} rules={rules.data ?? []} unread={unread} editable={operator} onTab={setTab} /> : activeTab === 'rules' ? <RulesWorkspace rules={rules.data ?? []} metrics={metrics.data?.metrics ?? []} /> : <NotificationsWorkspace editable={operator} notifications={notifications} />}</div>;
} }

View File

@@ -6409,6 +6409,13 @@
padding: 8px 7px 16px; padding: 8px 7px 16px;
} }
.v2-alert-page.is-events {
height: 100%;
min-height: 0;
overflow: hidden;
padding-bottom: 8px;
}
.v2-alert-navigation { .v2-alert-navigation {
position: sticky; position: sticky;
z-index: 8; z-index: 8;
@@ -6460,7 +6467,7 @@
} }
.v2-alert-context-card.semi-card { .v2-alert-context-card.semi-card {
min-height: 0; min-height: 94px;
} }
.v2-alert-context-card > .semi-card-body { .v2-alert-context-card > .semi-card-body {
@@ -6468,78 +6475,208 @@
} }
.v2-alert-context-card .v2-alert-kpis { .v2-alert-context-card .v2-alert-kpis {
min-height: 88px; height: 58px;
min-height: 58px;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.v2-alert-context-card .v2-alert-kpis .v2-metric-action, .v2-alert-context-card .v2-alert-kpis .v2-metric-action,
.v2-alert-context-card .v2-alert-kpis .v2-metric-action-content { .v2-alert-context-card .v2-alert-kpis .v2-metric-action-content {
min-height: 44px; min-height: 58px;
}
.v2-alert-context-card .v2-alert-kpis .v2-metric-action {
padding: 7px 5px;
}
.v2-alert-context-card .v2-alert-kpis .v2-metric-action.is-notice {
grid-column: auto;
}
.v2-alert-context-card .v2-alert-kpis .v2-metric-action-content {
display: grid;
align-content: center;
gap: 4px;
}
.v2-alert-context-card .v2-alert-kpis small {
font-size: 8px;
}
.v2-alert-context-card .v2-alert-kpis strong {
margin-top: 0;
font-size: 16px;
} }
.v2-alert-context-card .v2-alert-secondary-states { .v2-alert-context-card .v2-alert-secondary-states {
min-height: 34px;
border-top: 1px solid #e4e9f0; border-top: 1px solid #e4e9f0;
border-left: 0; border-left: 0;
padding: 4px; padding: 3px 4px;
} }
.v2-alert-context-card .v2-alert-secondary-states > .semi-button { .v2-alert-context-card .v2-alert-secondary-states > .semi-button {
min-height: 40px; min-height: 30px;
padding: 3px 5px; padding: 2px 5px;
}
.v2-alert-context-card .v2-alert-secondary-states span {
font-size: 8px;
}
.v2-alert-context-card .v2-alert-secondary-states b {
font-size: 12px;
}
.v2-alert-page.is-events .v2-alert-workspace {
min-height: 0;
flex: 1 1 0;
grid-template-columns: minmax(0, 1fr);
overflow: hidden;
} }
.v2-alert-table-card { .v2-alert-table-card {
min-height: 460px; height: 100%;
min-height: 0;
} }
.v2-alert-table-card > .semi-card-body > .v2-workspace-panel-header { .v2-alert-table-card > .semi-card-body > .v2-workspace-panel-header {
min-height: 58px; min-height: 50px;
align-items: flex-start; align-items: center;
padding: 8px; padding: 6px 8px;
} }
.v2-alert-table-card .v2-workspace-panel-copy > .semi-typography { .v2-alert-table-card .v2-workspace-panel-copy > .semi-typography {
display: none; display: none;
} }
.v2-alert-table-scroll.is-mobile-scroll {
min-height: 0;
flex: 1 1 0;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
scrollbar-width: auto;
touch-action: pan-y;
-webkit-overflow-scrolling: touch;
}
.v2-alert-table-scroll.is-mobile-scroll:focus-visible {
outline: 2px solid rgba(18, 104, 243, .34);
outline-offset: -2px;
}
.v2-alert-table-scroll.is-mobile-scroll::-webkit-scrollbar {
width: 11px;
}
.v2-alert-table-scroll.is-mobile-scroll::-webkit-scrollbar-thumb {
border: 3px solid transparent;
border-radius: 999px;
background: #9cacc0;
background-clip: padding-box;
}
.v2-alert-mobile-list { .v2-alert-mobile-list {
gap: 7px; gap: 6px;
padding: 7px; padding: 6px;
}
.v2-alert-mobile-list > .v2-alert-mobile-card.semi-card {
border-radius: 9px;
contain-intrinsic-size: auto 110px;
} }
.v2-alert-mobile-card-content { .v2-alert-mobile-card-content {
padding: 11px; padding: 8px 9px;
}
.v2-alert-mobile-card-content header > strong {
font-size: 13px;
}
.v2-alert-mobile-card-content header > span {
gap: 3px;
}
.v2-alert-mobile-card-content .v2-alert-severity.semi-tag,
.v2-alert-mobile-card-content .v2-alert-status.semi-tag {
height: 20px;
min-height: 20px;
border-radius: 5px;
padding-inline: 5px;
font-size: 8px;
} }
.v2-alert-mobile-card-content p { .v2-alert-mobile-card-content p {
margin: 6px 0; margin: 4px 0 5px;
overflow: hidden;
color: #405168;
font-size: 11px;
line-height: 14px;
text-overflow: ellipsis;
white-space: nowrap;
} }
.v2-alert-mobile-card-content dl { .v2-alert-mobile-card-content dl {
gap: 6px 10px; grid-template-columns: repeat(4, minmax(0, 1fr));
padding-top: 7px; gap: 0;
padding-top: 6px;
} }
.v2-alert-mobile-card-content dl > div { .v2-alert-mobile-card-content dl > div {
display: grid; display: block;
min-width: 0; min-width: 0;
min-height: 24px; min-height: 28px;
grid-template-columns: auto minmax(0, 1fr); padding: 0 5px;
align-items: center; }
gap: 6px;
.v2-alert-mobile-card-content dl > div:first-child {
padding-left: 0;
}
.v2-alert-mobile-card-content dl > div + div {
border-left: 1px solid #edf1f5;
}
.v2-alert-mobile-card-content dt {
overflow: hidden;
font-size: 8px;
text-overflow: ellipsis;
white-space: nowrap;
} }
.v2-alert-mobile-card-content dd { .v2-alert-mobile-card-content dd {
min-width: 0; min-width: 0;
margin: 0; margin: 3px 0 0;
overflow: hidden; overflow: hidden;
text-align: right; font-size: 9px;
line-height: 12px;
text-align: left;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.v2-alert-mobile-card-content footer { .v2-alert-mobile-card-content footer {
margin-top: 6px; display: none;
}
.v2-alert-table-card > .semi-card-body > footer:has(.v2-table-pagination-controls) {
min-height: 50px;
flex-direction: row;
align-items: center;
padding: 5px 6px;
}
.v2-alert-table-card > .semi-card-body > footer .v2-table-pagination-info {
display: none;
}
.v2-alert-table-card > .semi-card-body > footer .v2-table-pagination-button.semi-button,
.v2-alert-table-card > .semi-card-body > footer .v2-table-pagination-current,
.v2-alert-table-card > .semi-card-body > footer .v2-table-pagination-size.semi-select {
height: 34px;
} }
.v2-alert-rules { .v2-alert-rules {
@@ -7456,11 +7593,11 @@
@media (max-width: 680px) { @media (max-width: 680px) {
.v2-access-page-v3 { .v2-access-page-v3 {
height: auto; height: 100%;
min-height: 100%; min-height: 0;
gap: 7px; gap: 7px;
overflow: auto; overflow: hidden;
padding: 7px 7px 14px; padding: 7px 7px 8px;
} }
.v2-access-filter-card-v3 > .semi-card-body > .v2-workspace-filter-heading { .v2-access-filter-card-v3 > .semi-card-body > .v2-workspace-filter-heading {
@@ -7482,9 +7619,195 @@
display: block; display: block;
} }
.v2-access-kpis-card-v3.semi-card,
.v2-access-kpis-v3 {
min-height: 62px;
}
.v2-access-kpis-v3 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.v2-access-kpis-v3 .v2-metric-action {
min-height: 62px;
padding: 7px 6px;
}
.v2-access-kpis-v3 .v2-metric-action-content {
display: grid;
align-content: center;
gap: 4px;
}
.v2-access-kpis-v3 small {
overflow: hidden;
font-size: 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-access-kpis-v3 strong {
margin-left: 0;
font-size: 16px;
}
.v2-access-kpis-v3 em {
display: none;
}
.v2-access-workspace-v3 { .v2-access-workspace-v3 {
min-height: 540px; min-height: 0;
flex: none; flex: 1 1 0;
overflow: hidden;
}
.v2-access-table-v3 > .semi-card-body > .v2-workspace-panel-header {
min-height: 50px;
align-items: center;
padding: 6px 8px;
}
.v2-access-table-v3 .v2-workspace-panel-copy > .semi-typography {
display: none;
}
.v2-access-table-scroll-v3.is-mobile-scroll {
min-height: 0;
flex: 1 1 0;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
scrollbar-width: auto;
touch-action: pan-y;
-webkit-overflow-scrolling: touch;
}
.v2-access-table-scroll-v3.is-mobile-scroll:focus-visible {
outline: 2px solid rgba(18, 104, 243, .34);
outline-offset: -2px;
}
.v2-access-table-scroll-v3.is-mobile-scroll::-webkit-scrollbar {
width: 11px;
}
.v2-access-table-scroll-v3.is-mobile-scroll::-webkit-scrollbar-thumb {
border: 3px solid transparent;
border-radius: 999px;
background: #9cacc0;
background-clip: padding-box;
}
.v2-access-mobile-list {
gap: 6px;
padding: 6px;
}
.v2-access-mobile-card.semi-card {
border-radius: 9px;
contain-intrinsic-size: auto 126px;
}
.v2-access-mobile-card-content {
gap: 5px;
padding: 8px 9px;
}
.v2-access-mobile-card-content > header {
align-items: center;
}
.v2-access-mobile-card-content > header strong {
font-size: 13px;
}
.v2-access-mobile-card-content > header small {
font-size: 8px;
}
.v2-access-mobile-card-content > header .v2-access-connection {
gap: 0;
}
.v2-access-mobile-card-content > header .v2-access-connection > span {
display: none;
}
.v2-access-mobile-card-content > header .v2-access-connection-tag.semi-tag {
min-height: 20px;
border-radius: 5px;
padding-inline: 5px;
font-size: 8px;
}
.v2-access-mobile-card-content > p {
overflow: hidden;
font-size: 9px;
line-height: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-access-mobile-protocols {
gap: 4px;
padding-top: 5px;
}
.v2-access-mobile-list .v2-access-protocol-cell {
grid-template-columns: minmax(0, 1fr) auto;
gap: 2px 4px;
border-radius: 6px;
padding: 5px 6px;
}
.v2-access-mobile-list .v2-access-protocol-label {
min-width: 0;
overflow: hidden;
color: #50627a;
font-size: 8px;
line-height: 11px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-access-mobile-list .v2-access-protocol-cell > span {
justify-self: end;
gap: 3px;
font-size: 8px;
line-height: 11px;
}
.v2-access-mobile-list .v2-access-protocol-cell > span i {
width: 5px;
height: 5px;
}
.v2-access-mobile-list .v2-access-protocol-cell > strong {
grid-column: 1 / -1;
font-size: 8px;
line-height: 11px;
}
.v2-access-mobile-card-content > footer {
display: none;
}
.v2-access-table-v3 > .semi-card-body > footer:has(.v2-table-pagination-controls) {
min-height: 50px;
flex-direction: row;
align-items: center;
padding: 5px 6px;
}
.v2-access-table-v3 > .semi-card-body > footer .v2-table-pagination-info {
display: none;
}
.v2-access-table-v3 > .semi-card-body > footer .v2-table-pagination-button.semi-button,
.v2-access-table-v3 > .semi-card-body > footer .v2-table-pagination-current,
.v2-access-table-v3 > .semi-card-body > footer .v2-table-pagination-size.semi-select {
height: 34px;
} }
.v2-ops-page { .v2-ops-page {