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
} aria-haspopup="dialog" aria-expanded={open} aria-controls="v2-mileage-source-strategy" title={`当前优先:${primarySource?.label ?? '未配置'}`} onClick={() => setOpen((current) => !current)}>
数据源{primarySource?.protocol ?? '未配置'} 优先{enabled.length}/3
- 数据源策略同车同日按优先级选择一个里程来源}
+ 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}` : '已禁用'}
} disabled={index === 0} onClick={() => move(index, -1)} />} disabled={index === value.length - 1} onClick={() => move(index, 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;
+ }
+}