From 7eca5ae352ed2146a415bfee63b1107535474422 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 05:16:55 +0800 Subject: [PATCH] feat: unify configuration side sheets --- .../apps/web/src/v2/pages/HistoryPage.tsx | 26 +- .../apps/web/src/v2/pages/OperationsPage.tsx | 29 +- .../web/src/v2/pages/StatisticsPage.test.tsx | 2 +- .../apps/web/src/v2/pages/StatisticsPage.tsx | 24 +- .../apps/web/src/v2/productionEntry.test.ts | 14 +- .../src/v2/shared/WorkspaceSideSheet.test.tsx | 40 ++ .../web/src/v2/shared/WorkspaceSideSheet.tsx | 120 ++++++ .../apps/web/src/v2/styles/workspace.css | 365 ++++++++++++++++++ 8 files changed, 597 insertions(+), 23 deletions(-) create mode 100644 vehicle-data-platform/apps/web/src/v2/shared/WorkspaceSideSheet.test.tsx create mode 100644 vehicle-data-platform/apps/web/src/v2/shared/WorkspaceSideSheet.tsx diff --git a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx index 8331e8e8..b4d49b52 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx @@ -19,6 +19,7 @@ import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; +import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; import { detailTriggerRow } from '../shared/detailTriggerRow'; import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy'; import { usePlatformSession } from '../auth/AuthGate'; @@ -195,23 +196,36 @@ function ColumnVisibilityPanel({ metrics, visible, visibleKeys, onToggle, onShow return metrics.filter((metric) => `${metric.label} ${metric.key} ${metric.unit} ${metric.description ?? ''} ${metric.valueMappings?.map((item) => `${item.value} ${item.label}`).join(' ') ?? ''}`.toLowerCase().includes(keyword)); }, [metrics, search]); const displayedMetrics = filteredMetrics.slice(0, 200); - useSideSheetA11y(visible, '.v2-history-column-sidesheet', 'v2-history-column-settings', '列显示设置', '关闭列显示设置'); - return metrics.filter((metric) => metric.defaultVisible).length, [metrics]); + return 列显示设置基础身份与时间列保持显示} + title="列显示设置" + description="基础身份与时间列保持显示" + badge={`${visibleKeys.length} / ${metrics.length} 已选`} + badgeColor={visibleKeys.length ? 'blue' : 'grey'} + summaryItems={[ + { label: '当前显示', value: `${visibleKeys.length} 列`, detail: '业务字段', tone: 'primary' }, + { label: '可选字段', value: `${metrics.length} 项`, detail: '随数据类型变化' }, + { label: '默认字段', value: `${defaultMetricCount} 项`, detail: '可随时恢复', tone: 'success' } + ]} onCancel={onClose} - footer={
已选 {visibleKeys.length} / {metrics.length}
} + footerNote={`已选 ${visibleKeys.length} / ${metrics.length} 个业务字段`} + secondaryActions={[ + { label: '显示全部', onClick: onShowAll, disabled: !metrics.length }, + { label: '恢复默认', onClick: onReset, disabled: !metrics.length } + ]} + primaryAction={{ label: '完成', onClick: onClose }} >
{displayedMetrics.map((metric) => )}{!metrics.length ?

当前数据类型没有可配置指标

: search && !filteredMetrics.length ?

没有匹配字段

: filteredMetrics.length > displayedMetrics.length ?

当前仅展示前 200 项,请输入字段名或 key 缩小范围。

: null}
-
; + ; } function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: HistoryDataRow[]; metrics: HistoryMetricDefinition[]; selectedRowID?: string; onSelect: (row: HistoryDataRow) => void }) { diff --git a/vehicle-data-platform/apps/web/src/v2/pages/OperationsPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/OperationsPage.tsx index 04a772bf..c192d048 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/OperationsPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/OperationsPage.tsx @@ -13,6 +13,7 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; +import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy'; import { useMobileLayout } from '../hooks/useMobileLayout'; import { useSideSheetA11y } from '../hooks/useSideSheetA11y'; @@ -163,18 +164,30 @@ function SourcePolicySheet({ vin, source, diagnostic, editable, onClose, onSaved onClose: () => void; onSaved: (next: VehicleSourceDiagnostic) => void; }) { - useSideSheetA11y(Boolean(source), '.v2-source-policy-sidesheet', 'v2-source-policy-sheet', '车辆来源策略', '关闭车辆来源策略'); - return {source.sourceLabel}{source.terminalLabel || source.protocol} · {source.sourceKind === 'CANONICAL' ? '仅维护提供方' : '来源策略'} : '来源策略'} + ariaLabel="车辆来源策略" + closeLabel="关闭车辆来源策略" + placement={mobileLayout ? 'bottom' : 'right'} + width={mobileLayout ? undefined : 460} + height={mobileLayout ? 'min(76dvh, 660px)' : undefined} + title={source?.sourceLabel ?? '来源策略'} + description={source ? `${source.terminalLabel || source.protocol} · ${source.sourceKind === 'CANONICAL' ? '仅维护提供方' : '来源策略'}` : '来源策略'} + badge={source ? source.enabled ? '策略已启用' : '策略已停用' : undefined} + badgeColor={source?.enabled ? 'green' : 'grey'} + summaryItems={source ? [ + { label: '实时状态', value: source.online ? '在线' : '离线', detail: source.qualityReason || source.qualityStatus || '等待质量判定', tone: source.online ? 'success' : 'warning' }, + { label: '当前优先级', value: source.priority, detail: source.enabled ? '参与来源选举' : '不参与来源选举', tone: source.enabled ? 'primary' : 'neutral' }, + { label: '选举结论', value: source.recommended ? '当前推荐' : source.selectedWithinProtocol ? '协议首选' : '备用来源', detail: source.selectionReason || '等待选举说明' } + ] : []} onCancel={onClose} - footer={null} + footerNote="策略修改需点击表单内的保存按钮后才会生效。" + primaryAction={{ label: '完成', onClick: onClose }} > - {source ?
{ onSaved(next); onClose(); }} />
: null} -
; + {source ?
{ onSaved(next); onClose(); }} />
: null} + ; } function SourceDiagnosticTable({ vin, sources, diagnostic, editable, onSaved }: { diff --git a/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.test.tsx index a33fc1f8..acd57dc8 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.test.tsx @@ -265,7 +265,7 @@ test('lets users disable mileage sources and persists the source priority', asyn expect(screen.getByText('优先级 1').closest('.semi-tag')).toHaveClass('v2-mileage-source-priority'); expect(screen.getByText('GPS 里程')).toBeInTheDocument(); expect(screen.getByText('GPS 里程').closest('.semi-tag')).toBeInTheDocument(); - expect(screen.getAllByText('仪表盘里程')).toHaveLength(2); + expect(screen.getAllByText('仪表盘里程')).toHaveLength(3); expect(screen.getByRole('button', { name: /数据源.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960'); fireEvent.click(screen.getByRole('switch', { name: '禁用 国标 GB32960' })); fireEvent.click(screen.getByRole('button', { name: '上移 宇通 MQTT' })); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.tsx index e9ed93fe..a0ea62f4 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/StatisticsPage.tsx @@ -1,5 +1,5 @@ import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconInfoCircle, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; -import { Button, Card, Empty, Input, SideSheet, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui'; +import { Button, Card, Empty, Input, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui'; import { useQuery } from '@tanstack/react-query'; import { type CSSProperties, FormEvent, type KeyboardEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; @@ -18,6 +18,7 @@ import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; +import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy'; import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext'; import { useMobileLayout } from '../hooks/useMobileLayout'; @@ -120,7 +121,6 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC const mobileLayout = useMobileLayout(); const enabled = value.filter((source) => source.enabled); const primarySource = enabled[0]; - useSideSheetA11y(open, '.v2-mileage-source-sidesheet', 'v2-mileage-source-strategy', '数据源策略配置', '关闭数据源策略'); const update = (sources: MileageSourceOption[]) => { onChange(sources); try { window.localStorage.setItem(SOURCE_STORAGE_KEY, JSON.stringify(sources)); } catch { /* preference persistence is optional */ } @@ -142,16 +142,26 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC - 数据源策略同车同日按优先级选择一个里程来源} + title="数据源策略" + description="同车同日按优先级选择一个里程来源" + badge={`${enabled.length}/3 已启用`} + badgeColor={enabled.length === 3 ? 'green' : 'blue'} + summaryItems={[ + { label: '当前首选', value: primarySource?.protocol ?? '未配置', detail: primarySource?.mileageType ?? '至少保留一个来源', tone: 'primary' }, + { label: '启用来源', value: `${enabled.length} / 3`, detail: enabled.map((source) => source.protocol).join(' · '), tone: enabled.length === 3 ? 'success' : 'neutral' }, + { label: '统计口径', value: '单车单日', detail: '仅采用一个优先来源' } + ]} onCancel={() => setOpen(false)} - footer={
同车同日只使用一个来源,避免重复累计;至少保留一个来源。
} + footerNote="避免多协议重复累计;至少保留一个来源。" + primaryAction={{ label: '完成', onClick: () => setOpen(false) }} >
{value.map((source, index) => toggle(source.protocol)} /> @@ -159,7 +169,7 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC {source.enabled ? `优先级 ${enabled.findIndex((item) => item.protocol === source.protocol) + 1}` : '已禁用'}
)}
-
+ ; } 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 67a9e9b6..2a646748 100644 --- a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts +++ b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts @@ -21,6 +21,7 @@ const mobileFilterSheetSource = readFileSync(resolve(process.cwd(), 'src/v2/shar const passwordInputSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/PasswordInput.tsx'), 'utf8'); const workspaceFilterPanelSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceFilterPanel.tsx'), 'utf8'); const workspaceMetricRailSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceMetricRail.tsx'), 'utf8'); +const workspaceSideSheetSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceSideSheet.tsx'), 'utf8'); const segmentedTabsSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/SegmentedTabs.tsx'), 'utf8'); const metricActionSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/MetricActionButton.tsx'), 'utf8'); const tablePaginationSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/TablePagination.tsx'), 'utf8'); @@ -316,6 +317,7 @@ describe('V2 production entry', () => { expect(corePageSources.OperationsPage).toContain('className="v2-source-table"'); expect(corePageSources.OperationsPage).toContain(' { expect(corePageSources.StatisticsPage).not.toContain('
{ expect(corePageSources[name]).toContain(' { + const onCancel = vi.fn(); + const onReset = vi.fn(); + const onComplete = vi.fn(); + render( + + ); + + const dialog = screen.getByRole('dialog', { name: '测试策略配置' }); + expect(dialog.closest('.semi-sidesheet')).toHaveClass('v2-workspace-config-sidesheet', 'test-config-sheet'); + expect(screen.getByText('统一维护优先级与启停状态')).toBeInTheDocument(); + expect(screen.getByRole('list', { name: '测试策略配置摘要' })).toHaveTextContent('当前首选来源 A健康状态正常实时上报'); + fireEvent.click(screen.getByRole('button', { name: '恢复默认' })); + fireEvent.click(screen.getByRole('button', { name: '完成' })); + fireEvent.click(screen.getByRole('button', { name: '关闭测试策略配置' })); + expect(onReset).toHaveBeenCalledTimes(1); + expect(onComplete).toHaveBeenCalledTimes(1); + expect(onCancel).toHaveBeenCalledTimes(1); +}); diff --git a/vehicle-data-platform/apps/web/src/v2/shared/WorkspaceSideSheet.tsx b/vehicle-data-platform/apps/web/src/v2/shared/WorkspaceSideSheet.tsx new file mode 100644 index 00000000..394a7bad --- /dev/null +++ b/vehicle-data-platform/apps/web/src/v2/shared/WorkspaceSideSheet.tsx @@ -0,0 +1,120 @@ +import { Button, SideSheet, Tag } from '@douyinfe/semi-ui'; +import { useId, useLayoutEffect, type ReactNode } from 'react'; + +export type WorkspaceSideSheetAction = { + label: string; + onClick: () => void; + disabled?: boolean; + loading?: boolean; + theme?: 'solid' | 'light' | 'borderless'; + type?: 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger'; +}; + +export type WorkspaceSideSheetSummaryItem = { + label: string; + value: ReactNode; + detail?: ReactNode; + tone?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger'; +}; + +type WorkspaceSideSheetProps = { + className?: string; + visible: boolean; + ariaLabel: string; + closeLabel?: string; + title: ReactNode; + description: ReactNode; + badge?: ReactNode; + badgeColor?: 'blue' | 'green' | 'orange' | 'red' | 'grey'; + placement?: 'left' | 'right' | 'top' | 'bottom'; + width?: string | number; + height?: string | number; + summaryItems?: WorkspaceSideSheetSummaryItem[]; + footerNote?: ReactNode; + secondaryActions?: WorkspaceSideSheetAction[]; + primaryAction?: WorkspaceSideSheetAction; + onCancel: () => void; + children: ReactNode; +}; + +export function WorkspaceSideSheet({ + className = '', + visible, + ariaLabel, + closeLabel = `关闭${ariaLabel}`, + title, + description, + badge, + badgeColor = 'blue', + placement = 'right', + width = 460, + height, + summaryItems = [], + footerNote, + secondaryActions = [], + primaryAction, + onCancel, + children +}: WorkspaceSideSheetProps) { + const sheetId = useId(); + const sheetClassName = ['v2-workspace-config-sidesheet', className].filter(Boolean).join(' '); + const hasFooter = Boolean(footerNote || secondaryActions.length || primaryAction); + + useLayoutEffect(() => { + if (!visible) return; + const apply = () => { + const dialog = document.querySelector(`[data-workspace-sheet-id="${sheetId}"] .semi-sidesheet-inner`); + dialog?.setAttribute('aria-label', ariaLabel); + dialog?.querySelector('.semi-sidesheet-close')?.setAttribute('aria-label', closeLabel); + }; + apply(); + const frame = window.requestAnimationFrame(apply); + return () => window.cancelAnimationFrame(frame); + }, [ariaLabel, closeLabel, sheetId, visible]); + + return + {title}{description} + {badge ? {badge} : null} + } + onCancel={onCancel} + footer={hasFooter ?
+ {footerNote} +
+ {secondaryActions.map((action) => )} + {primaryAction ? : null} +
+
: null} + > +
+ {summaryItems.length ?
+ {summaryItems.map((item) => + {item.label} + {item.value} + {item.detail ? {item.detail} : null} + )} +
: null} +
{children}
+
+
; +} diff --git a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css index e805f926..000965a4 100644 --- a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css +++ b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css @@ -20594,3 +20594,368 @@ min-height: 36px; } } + +/* + * Shared Semi UI configuration SideSheet: one hierarchy for strategy, + * column and source-policy work across desktop and mobile. + */ +.v2-workspace-config-sidesheet .semi-sidesheet-inner { + overflow: hidden; + border-left: 1px solid #d9e3ef; + background: #f5f7fa; + box-shadow: -22px 0 64px rgba(24, 43, 68, .16); +} + +.v2-workspace-config-sidesheet .semi-sidesheet-header { + min-height: 78px; + border-bottom: 1px solid #e1e8f1; + background: + radial-gradient(circle at 12% 0%, rgba(18, 104, 243, .08), transparent 34%), + linear-gradient(180deg, #fff 0%, #fbfcfe 100%); + padding: 14px 18px; +} + +.v2-workspace-config-sidesheet .semi-sidesheet-title { + min-width: 0; +} + +.v2-workspace-config-title { + display: flex; + width: 100%; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-right: 6px; +} + +.v2-workspace-config-title > span { + display: grid; + min-width: 0; + gap: 4px; +} + +.v2-workspace-config-title strong { + overflow: hidden; + color: #1f344c; + font-size: 19px; + font-weight: 750; + letter-spacing: -.025em; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-title small { + overflow: hidden; + color: #78879a; + font-size: 10px; + font-weight: 500; + line-height: 1.45; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-title > .semi-tag { + min-width: max-content; + min-height: 25px; + flex: 0 0 auto; + justify-content: center; + border-radius: 999px; + padding-inline: 9px; + font-size: 9px; + font-weight: 700; +} + +.v2-workspace-config-sidesheet .semi-sidesheet-body { + min-height: 0; + overflow: hidden; + background: #f5f7fa; + padding: 0; +} + +.v2-workspace-config-body { + display: flex; + height: 100%; + min-height: 0; + flex-direction: column; +} + +.v2-workspace-config-summary { + display: grid; + min-height: 84px; + flex: 0 0 auto; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1px; + border-bottom: 1px solid #e2e9f1; + background: #e2e9f1; +} + +.v2-workspace-config-summary > span { + position: relative; + display: grid; + min-width: 0; + align-content: center; + gap: 3px; + background: #fff; + padding: 11px 12px; +} + +.v2-workspace-config-summary > span::before { + position: absolute; + inset: 0 auto 0 0; + width: 2px; + background: transparent; + content: ''; +} + +.v2-workspace-config-summary > span.is-primary::before { background: #1268f3; } +.v2-workspace-config-summary > span.is-success::before { background: #18a76f; } +.v2-workspace-config-summary > span.is-warning::before { background: #f4a340; } +.v2-workspace-config-summary > span.is-danger::before { background: #e95a52; } + +.v2-workspace-config-summary small { + overflow: hidden; + color: #8794a6; + font-size: 9px; + font-weight: 600; + line-height: 1.35; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-summary strong { + overflow: hidden; + color: #263b53; + font-size: 14px; + font-weight: 750; + font-variant-numeric: tabular-nums; + line-height: 1.3; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-summary > span.is-primary strong { color: #1268f3; } +.v2-workspace-config-summary > span.is-success strong { color: #12845a; } +.v2-workspace-config-summary > span.is-warning strong { color: #b86f16; } +.v2-workspace-config-summary > span.is-danger strong { color: #c8423b; } + +.v2-workspace-config-summary em { + overflow: hidden; + color: #8a97a8; + font-size: 8px; + font-style: normal; + line-height: 1.4; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-content { + min-height: 0; + flex: 1 1 0; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + padding: 12px; + scrollbar-gutter: stable; +} + +.v2-workspace-config-sidesheet .semi-sidesheet-footer { + min-height: 64px; + border-top: 1px solid #e1e8f1; + background: rgba(255, 255, 255, .97); + padding: 10px 14px; +} + +.v2-workspace-config-footer { + display: flex; + width: 100%; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.v2-workspace-config-footer > span { + min-width: 0; + overflow: hidden; + color: #78879a; + font-size: 9px; + line-height: 1.45; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-workspace-config-footer > div { + display: flex; + min-width: max-content; + flex: 0 0 auto; + align-items: center; + gap: 7px; +} + +.v2-workspace-config-footer .semi-button { + min-width: 86px; + min-height: 38px; + border-radius: 8px; + font-size: 10px; + font-weight: 700; +} + +.v2-workspace-config-footer .semi-button-primary.semi-button-solid { + box-shadow: 0 7px 18px rgba(18, 104, 243, .18); +} + +.v2-history-column-sidesheet .v2-workspace-config-content { + overflow: hidden; + padding: 0; +} + +.v2-history-column-sidesheet .v2-history-column-search { + border-bottom-color: #e2e9f1; + background: #f8fafc; + padding: 10px 12px; +} + +.v2-history-column-sidesheet .v2-history-column-list { + display: grid; + align-content: start; + gap: 7px; + padding: 9px 11px 12px; +} + +.v2-history-column-sidesheet .v2-history-column-list > label { + min-height: 58px; + border: 1px solid #dfe7f0; + border-radius: 10px; + background: #fff; + padding: 8px 10px; + transition: border-color .16s ease, background .16s ease, box-shadow .16s ease; +} + +.v2-history-column-sidesheet .v2-history-column-list > label:hover { + border-color: #bfd1e8; + background: #fbfdff; + box-shadow: 0 6px 18px rgba(33, 55, 83, .05); +} + +.v2-history-column-sidesheet .v2-history-column-list > label:has(.semi-checkbox-checked) { + border-color: #c8daf5; + background: linear-gradient(120deg, #f4f8ff 0%, #fff 68%); +} + +.v2-mileage-source-sidesheet .v2-workspace-config-content { + padding: 11px; +} + +.v2-mileage-source-sidesheet .v2-mileage-source-list { + gap: 8px; +} + +.v2-mileage-source-sidesheet .v2-mileage-source-card.semi-card { + border-color: #dce5ef; + border-radius: 11px; + box-shadow: 0 6px 18px rgba(31, 53, 80, .045); +} + +.v2-source-policy-sidesheet .v2-source-policy-sheet-content { + gap: 10px; +} + +.v2-source-policy-sidesheet .v2-source-policy-cell { + overflow: hidden; + border: 1px solid #dce5ef; + border-radius: 11px; + background: #fff; + padding: 12px; + box-shadow: 0 7px 20px rgba(31, 53, 80, .05); +} + +@media (max-width: 680px) { + .v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-inner { + border: 1px solid #d8e2ed; + border-bottom: 0; + border-radius: 18px 18px 0 0; + box-shadow: 0 -18px 48px rgba(23, 43, 68, .17); + } + + .v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-header { + min-height: 68px; + padding: 10px 12px 10px 14px; + } + + .v2-workspace-config-title strong { + font-size: 17px; + } + + .v2-workspace-config-title small { + font-size: 9px; + } + + .v2-workspace-config-title > .semi-tag { + min-height: 23px; + padding-inline: 8px; + font-size: 8px; + } + + .v2-workspace-config-summary { + min-height: 68px; + } + + .v2-workspace-config-summary > span { + gap: 2px; + padding: 8px 9px; + } + + .v2-workspace-config-summary small { + font-size: 8px; + } + + .v2-workspace-config-summary strong { + font-size: 12px; + } + + .v2-workspace-config-summary em { + font-size: 7px; + } + + .v2-workspace-config-content { + padding: 8px; + } + + .v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-footer { + min-height: 58px; + padding: 7px 9px calc(7px + env(safe-area-inset-bottom)); + } + + .v2-workspace-config-footer > span { + display: none; + } + + .v2-workspace-config-footer > div { + width: 100%; + min-width: 0; + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(0, 1fr); + gap: 6px; + } + + .v2-workspace-config-footer .semi-button { + width: 100%; + min-width: 0; + min-height: 40px; + padding-inline: 7px; + font-size: 9px; + } + + .v2-history-column-sidesheet .v2-history-column-list { + gap: 6px; + padding: 7px 8px 10px; + } + + .v2-history-column-sidesheet .v2-history-column-list > label { + min-height: 54px; + padding: 7px 8px; + } +}