feat: unify configuration side sheets
This commit is contained in:
@@ -19,6 +19,7 @@ import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
|||||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||||
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
||||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||||
|
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||||
import { detailTriggerRow } from '../shared/detailTriggerRow';
|
import { detailTriggerRow } from '../shared/detailTriggerRow';
|
||||||
import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||||
import { usePlatformSession } from '../auth/AuthGate';
|
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));
|
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]);
|
}, [metrics, search]);
|
||||||
const displayedMetrics = filteredMetrics.slice(0, 200);
|
const displayedMetrics = filteredMetrics.slice(0, 200);
|
||||||
useSideSheetA11y(visible, '.v2-history-column-sidesheet', 'v2-history-column-settings', '列显示设置', '关闭列显示设置');
|
const defaultMetricCount = useMemo(() => metrics.filter((metric) => metric.defaultVisible).length, [metrics]);
|
||||||
return <SideSheet
|
return <WorkspaceSideSheet
|
||||||
className="v2-history-column-sidesheet"
|
className="v2-history-column-sidesheet"
|
||||||
visible={visible}
|
visible={visible}
|
||||||
aria-label="列显示设置"
|
ariaLabel="列显示设置"
|
||||||
placement={mobileLayout ? 'bottom' : 'right'}
|
placement={mobileLayout ? 'bottom' : 'right'}
|
||||||
width={mobileLayout ? undefined : 460}
|
width={mobileLayout ? undefined : 460}
|
||||||
height={mobileLayout ? 'min(68dvh, 580px)' : undefined}
|
height={mobileLayout ? 'min(68dvh, 580px)' : undefined}
|
||||||
title={<div className="v2-history-column-title"><strong>列显示设置</strong><span>基础身份与时间列保持显示</span></div>}
|
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}
|
onCancel={onClose}
|
||||||
footer={<div className="v2-history-column-footer"><span>已选 {visibleKeys.length} / {metrics.length}</span><Button theme="light" onClick={onShowAll} disabled={!metrics.length}>显示全部</Button><Button theme="light" onClick={onReset} disabled={!metrics.length}>恢复默认</Button></div>}
|
footerNote={`已选 ${visibleKeys.length} / ${metrics.length} 个业务字段`}
|
||||||
|
secondaryActions={[
|
||||||
|
{ label: '显示全部', onClick: onShowAll, disabled: !metrics.length },
|
||||||
|
{ label: '恢复默认', onClick: onReset, disabled: !metrics.length }
|
||||||
|
]}
|
||||||
|
primaryAction={{ label: '完成', onClick: onClose }}
|
||||||
>
|
>
|
||||||
<div className="v2-history-column-content">
|
<div className="v2-history-column-content">
|
||||||
<label className="v2-history-column-search"><Input prefix={<IconSearch />} value={search} onChange={setSearch} placeholder="搜索中文、状态含义或字段 key" /></label>
|
<label className="v2-history-column-search"><Input prefix={<IconSearch />} value={search} onChange={setSearch} placeholder="搜索中文、状态含义或字段 key" /></label>
|
||||||
<div className="v2-history-column-list">{displayedMetrics.map((metric) => <label key={metric.key} title={metric.description}><Checkbox aria-label={`${metric.label}${metric.unit ? ` (${metric.unit})` : ''}`} checked={visibleKeys.includes(metric.key)} onChange={() => onToggle(metric.key)} /><span>{metric.label}{metric.unit ? ` (${metric.unit})` : ''}{metric.valueMappings?.length ? <em>{metric.valueMappings.slice(0, 4).map((item) => `${item.value}=${item.label}`).join(' · ')}</em> : null}</span><small>{metric.key}</small></label>)}{!metrics.length ? <p>当前数据类型没有可配置指标</p> : search && !filteredMetrics.length ? <p>没有匹配字段</p> : filteredMetrics.length > displayedMetrics.length ? <p>当前仅展示前 200 项,请输入字段名或 key 缩小范围。</p> : null}</div>
|
<div className="v2-history-column-list">{displayedMetrics.map((metric) => <label key={metric.key} title={metric.description}><Checkbox aria-label={`${metric.label}${metric.unit ? ` (${metric.unit})` : ''}`} checked={visibleKeys.includes(metric.key)} onChange={() => onToggle(metric.key)} /><span>{metric.label}{metric.unit ? ` (${metric.unit})` : ''}{metric.valueMappings?.length ? <em>{metric.valueMappings.slice(0, 4).map((item) => `${item.value}=${item.label}`).join(' · ')}</em> : null}</span><small>{metric.key}</small></label>)}{!metrics.length ? <p>当前数据类型没有可配置指标</p> : search && !filteredMetrics.length ? <p>没有匹配字段</p> : filteredMetrics.length > displayedMetrics.length ? <p>当前仅展示前 200 项,请输入字段名或 key 缩小范围。</p> : null}</div>
|
||||||
</div>
|
</div>
|
||||||
</SideSheet>;
|
</WorkspaceSideSheet>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: HistoryDataRow[]; metrics: HistoryMetricDefinition[]; selectedRowID?: string; onSelect: (row: HistoryDataRow) => void }) {
|
function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: HistoryDataRow[]; metrics: HistoryMetricDefinition[]; selectedRowID?: string; onSelect: (row: HistoryDataRow) => void }) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
|||||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||||
|
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||||
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
|
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
|
||||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||||
@@ -163,18 +164,30 @@ function SourcePolicySheet({ vin, source, diagnostic, editable, onClose, onSaved
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSaved: (next: VehicleSourceDiagnostic) => void;
|
onSaved: (next: VehicleSourceDiagnostic) => void;
|
||||||
}) {
|
}) {
|
||||||
useSideSheetA11y(Boolean(source), '.v2-source-policy-sidesheet', 'v2-source-policy-sheet', '车辆来源策略', '关闭车辆来源策略');
|
const mobileLayout = useMobileLayout();
|
||||||
return <SideSheet
|
return <WorkspaceSideSheet
|
||||||
className="v2-source-policy-sidesheet"
|
className="v2-source-policy-sidesheet"
|
||||||
visible={Boolean(source)}
|
visible={Boolean(source)}
|
||||||
width={440}
|
ariaLabel="车辆来源策略"
|
||||||
aria-label="车辆来源策略"
|
closeLabel="关闭车辆来源策略"
|
||||||
title={source ? <div className="v2-source-policy-sheet-title"><strong>{source.sourceLabel}</strong><span>{source.terminalLabel || source.protocol} · {source.sourceKind === 'CANONICAL' ? '仅维护提供方' : '来源策略'}</span></div> : '来源策略'}
|
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}
|
onCancel={onClose}
|
||||||
footer={null}
|
footerNote="策略修改需点击表单内的保存按钮后才会生效。"
|
||||||
|
primaryAction={{ label: '完成', onClick: onClose }}
|
||||||
>
|
>
|
||||||
{source ? <div className="v2-source-policy-sheet-content"><div className="v2-source-policy-sheet-summary"><SourceHealth source={source} /><SourceDecision source={source} /></div><SourcePolicyEditor vin={vin} source={source} diagnostic={diagnostic} editable={editable && Boolean(source.sourceRef)} onSaved={(next) => { onSaved(next); onClose(); }} /></div> : null}
|
{source ? <div className="v2-source-policy-sheet-content"><SourcePolicyEditor vin={vin} source={source} diagnostic={diagnostic} editable={editable && Boolean(source.sourceRef)} onSaved={(next) => { onSaved(next); onClose(); }} /></div> : null}
|
||||||
</SideSheet>;
|
</WorkspaceSideSheet>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SourceDiagnosticTable({ vin, sources, diagnostic, editable, onSaved }: {
|
function SourceDiagnosticTable({ vin, sources, diagnostic, editable, onSaved }: {
|
||||||
|
|||||||
@@ -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('优先级 1').closest('.semi-tag')).toHaveClass('v2-mileage-source-priority');
|
||||||
expect(screen.getByText('GPS 里程')).toBeInTheDocument();
|
expect(screen.getByText('GPS 里程')).toBeInTheDocument();
|
||||||
expect(screen.getByText('GPS 里程').closest('.semi-tag')).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');
|
expect(screen.getByRole('button', { name: /数据源.*GB32960 优先.*3\/3/ })).toHaveAttribute('title', '当前优先:国标 GB32960');
|
||||||
fireEvent.click(screen.getByRole('switch', { name: '禁用 国标 GB32960' }));
|
fireEvent.click(screen.getByRole('switch', { name: '禁用 国标 GB32960' }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: '上移 宇通 MQTT' }));
|
fireEvent.click(screen.getByRole('button', { name: '上移 宇通 MQTT' }));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconInfoCircle, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
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 { useQuery } from '@tanstack/react-query';
|
||||||
import { type CSSProperties, FormEvent, type KeyboardEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
import { type CSSProperties, FormEvent, type KeyboardEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
@@ -18,6 +18,7 @@ import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
|||||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||||
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
||||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||||
|
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||||
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||||
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
|
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
|
||||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||||
@@ -120,7 +121,6 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC
|
|||||||
const mobileLayout = useMobileLayout();
|
const mobileLayout = useMobileLayout();
|
||||||
const enabled = value.filter((source) => source.enabled);
|
const enabled = value.filter((source) => source.enabled);
|
||||||
const primarySource = enabled[0];
|
const primarySource = enabled[0];
|
||||||
useSideSheetA11y(open, '.v2-mileage-source-sidesheet', 'v2-mileage-source-strategy', '数据源策略配置', '关闭数据源策略');
|
|
||||||
const update = (sources: MileageSourceOption[]) => {
|
const update = (sources: MileageSourceOption[]) => {
|
||||||
onChange(sources);
|
onChange(sources);
|
||||||
try { window.localStorage.setItem(SOURCE_STORAGE_KEY, JSON.stringify(sources)); } catch { /* preference persistence is optional */ }
|
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
|
|||||||
<Button className="v2-mileage-source-trigger" theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-expanded={open} aria-controls="v2-mileage-source-strategy" title={`当前优先:${primarySource?.label ?? '未配置'}`} onClick={() => setOpen((current) => !current)}>
|
<Button className="v2-mileage-source-trigger" theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-expanded={open} aria-controls="v2-mileage-source-strategy" title={`当前优先:${primarySource?.label ?? '未配置'}`} onClick={() => setOpen((current) => !current)}>
|
||||||
<span>数据源</span><em>{primarySource?.protocol ?? '未配置'} 优先</em><b>{enabled.length}/3</b>
|
<span>数据源</span><em>{primarySource?.protocol ?? '未配置'} 优先</em><b>{enabled.length}/3</b>
|
||||||
</Button>
|
</Button>
|
||||||
<SideSheet
|
<WorkspaceSideSheet
|
||||||
className="v2-mileage-source-sidesheet"
|
className="v2-mileage-source-sidesheet"
|
||||||
visible={open}
|
visible={open}
|
||||||
aria-label="数据源策略"
|
ariaLabel="数据源策略配置"
|
||||||
|
closeLabel="关闭数据源策略"
|
||||||
placement={mobileLayout ? 'bottom' : 'right'}
|
placement={mobileLayout ? 'bottom' : 'right'}
|
||||||
width={mobileLayout ? undefined : 430}
|
width={mobileLayout ? undefined : 430}
|
||||||
height={mobileLayout ? 'min(58dvh, 500px)' : undefined}
|
height={mobileLayout ? 'min(58dvh, 500px)' : undefined}
|
||||||
title={<div className="v2-mileage-source-title"><strong>数据源策略</strong><span>同车同日按优先级选择一个里程来源</span></div>}
|
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)}
|
onCancel={() => setOpen(false)}
|
||||||
footer={<div className="v2-mileage-source-footer"><span>同车同日只使用一个来源,避免重复累计;至少保留一个来源。</span><Button theme="solid" onClick={() => setOpen(false)}>完成</Button></div>}
|
footerNote="避免多协议重复累计;至少保留一个来源。"
|
||||||
|
primaryAction={{ label: '完成', onClick: () => setOpen(false) }}
|
||||||
>
|
>
|
||||||
<div className="v2-mileage-source-list">{value.map((source, index) => <Card key={source.protocol} className={`v2-mileage-source-card${source.enabled ? '' : ' is-disabled'}`} bodyStyle={{ padding: 0 }}>
|
<div className="v2-mileage-source-list">{value.map((source, index) => <Card key={source.protocol} className={`v2-mileage-source-card${source.enabled ? '' : ' is-disabled'}`} bodyStyle={{ padding: 0 }}>
|
||||||
<Switch className="v2-mileage-source-switch" checked={source.enabled} aria-label={`${source.enabled ? '禁用' : '启用'} ${source.label}`} onChange={() => toggle(source.protocol)} />
|
<Switch className="v2-mileage-source-switch" checked={source.enabled} aria-label={`${source.enabled ? '禁用' : '启用'} ${source.label}`} onChange={() => toggle(source.protocol)} />
|
||||||
@@ -159,7 +169,7 @@ function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onC
|
|||||||
<Tag className="v2-mileage-source-priority" color={source.enabled ? 'blue' : 'grey'} type="light" size="small">{source.enabled ? `优先级 ${enabled.findIndex((item) => item.protocol === source.protocol) + 1}` : '已禁用'}</Tag>
|
<Tag className="v2-mileage-source-priority" color={source.enabled ? 'blue' : 'grey'} type="light" size="small">{source.enabled ? `优先级 ${enabled.findIndex((item) => item.protocol === source.protocol) + 1}` : '已禁用'}</Tag>
|
||||||
<div className="v2-mileage-source-order"><Button theme="borderless" aria-label={`上移 ${source.label}`} icon={<IconArrowUp />} disabled={index === 0} onClick={() => move(index, -1)} /><Button theme="borderless" aria-label={`下移 ${source.label}`} icon={<IconArrowDown />} disabled={index === value.length - 1} onClick={() => move(index, 1)} /></div>
|
<div className="v2-mileage-source-order"><Button theme="borderless" aria-label={`上移 ${source.label}`} icon={<IconArrowUp />} disabled={index === 0} onClick={() => move(index, -1)} /><Button theme="borderless" aria-label={`下移 ${source.label}`} icon={<IconArrowDown />} disabled={index === value.length - 1} onClick={() => move(index, 1)} /></div>
|
||||||
</Card>)}</div>
|
</Card>)}</div>
|
||||||
</SideSheet>
|
</WorkspaceSideSheet>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 passwordInputSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/PasswordInput.tsx'), 'utf8');
|
||||||
const workspaceFilterPanelSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceFilterPanel.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 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 segmentedTabsSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/SegmentedTabs.tsx'), 'utf8');
|
||||||
const metricActionSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/MetricActionButton.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');
|
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('className="v2-source-table"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Table');
|
expect(corePageSources.OperationsPage).toContain('<Table');
|
||||||
expect(corePageSources.OperationsPage).toContain('<SideSheet');
|
expect(corePageSources.OperationsPage).toContain('<SideSheet');
|
||||||
|
expect(corePageSources.OperationsPage).toContain('<WorkspaceSideSheet');
|
||||||
expect(corePageSources.OperationsPage).toContain('v2-source-policy-sidesheet');
|
expect(corePageSources.OperationsPage).toContain('v2-source-policy-sidesheet');
|
||||||
expect(corePageSources.OperationsPage).toContain('className={`v2-source-mobile-card');
|
expect(corePageSources.OperationsPage).toContain('className={`v2-source-mobile-card');
|
||||||
expect(corePageSources.OperationsPage).not.toContain('<table className="v2-source-table"');
|
expect(corePageSources.OperationsPage).not.toContain('<table className="v2-source-table"');
|
||||||
@@ -377,7 +379,7 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources.StatisticsPage).not.toContain('<section className="v2-mileage-query-panel"');
|
expect(corePageSources.StatisticsPage).not.toContain('<section className="v2-mileage-query-panel"');
|
||||||
expect(corePageSources.StatisticsPage).not.toContain('<section className="v2-mileage-results"');
|
expect(corePageSources.StatisticsPage).not.toContain('<section className="v2-mileage-results"');
|
||||||
expect(corePageSources.StatisticsPage).toContain('<Tag');
|
expect(corePageSources.StatisticsPage).toContain('<Tag');
|
||||||
expect(corePageSources.StatisticsPage).toContain('<SideSheet');
|
expect(corePageSources.StatisticsPage).toContain('<WorkspaceSideSheet');
|
||||||
expect(corePageSources.StatisticsPage).toContain('v2-mileage-source-sidesheet');
|
expect(corePageSources.StatisticsPage).toContain('v2-mileage-source-sidesheet');
|
||||||
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-filter-sidesheet"');
|
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-filter-sidesheet"');
|
||||||
expect(corePageSources.StatisticsPage).toContain('<MobileFilterToggle');
|
expect(corePageSources.StatisticsPage).toContain('<MobileFilterToggle');
|
||||||
@@ -388,6 +390,16 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources[name]).toContain('<MobileFilterSheet');
|
expect(corePageSources[name]).toContain('<MobileFilterSheet');
|
||||||
}
|
}
|
||||||
expect(vehicleSearchSource).toContain("from '../shared/MobileFilterSheet'");
|
expect(vehicleSearchSource).toContain("from '../shared/MobileFilterSheet'");
|
||||||
|
for (const name of ['HistoryPage', 'StatisticsPage', 'OperationsPage']) {
|
||||||
|
expect(corePageSources[name]).toContain("from '../shared/WorkspaceSideSheet'");
|
||||||
|
expect(corePageSources[name]).toContain('<WorkspaceSideSheet');
|
||||||
|
}
|
||||||
|
expect(workspaceSideSheetSource).toContain("from '@douyinfe/semi-ui'");
|
||||||
|
expect(workspaceSideSheetSource).toContain('<SideSheet');
|
||||||
|
expect(workspaceSideSheetSource).toContain('className={sheetClassName}');
|
||||||
|
expect(workspaceStyles).toContain('.v2-workspace-config-sidesheet .semi-sidesheet-inner {');
|
||||||
|
expect(workspaceStyles).toContain('.v2-workspace-config-summary {');
|
||||||
|
expect(workspaceStyles).toContain('.v2-workspace-config-footer {');
|
||||||
expect(vehicleSearchSource).toContain('<MobileFilterSheet');
|
expect(vehicleSearchSource).toContain('<MobileFilterSheet');
|
||||||
expect(corePageSources.StatisticsPage).not.toContain('v2-mileage-source-popover');
|
expect(corePageSources.StatisticsPage).not.toContain('v2-mileage-source-popover');
|
||||||
expect(corePageSources.StatisticsPage).toContain('<Table className="v2-mileage-table"');
|
expect(corePageSources.StatisticsPage).toContain('<Table className="v2-mileage-table"');
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { afterEach, expect, test, vi } from 'vitest';
|
||||||
|
import { WorkspaceSideSheet } from './WorkspaceSideSheet';
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
test('renders one accessible Semi configuration sheet with shared summary and actions', () => {
|
||||||
|
const onCancel = vi.fn();
|
||||||
|
const onReset = vi.fn();
|
||||||
|
const onComplete = vi.fn();
|
||||||
|
render(<WorkspaceSideSheet
|
||||||
|
className="test-config-sheet"
|
||||||
|
visible
|
||||||
|
ariaLabel="测试策略配置"
|
||||||
|
title="策略设置"
|
||||||
|
description="统一维护优先级与启停状态"
|
||||||
|
badge="2 项已启用"
|
||||||
|
summaryItems={[
|
||||||
|
{ label: '当前首选', value: '来源 A', tone: 'primary' },
|
||||||
|
{ label: '健康状态', value: '正常', detail: '实时上报', tone: 'success' }
|
||||||
|
]}
|
||||||
|
footerNote="修改只在确认后生效"
|
||||||
|
secondaryActions={[{ label: '恢复默认', onClick: onReset }]}
|
||||||
|
primaryAction={{ label: '完成', onClick: onComplete }}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<label>优先级<input aria-label="优先级" /></label>
|
||||||
|
</WorkspaceSideSheet>);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
@@ -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 <SideSheet
|
||||||
|
className={sheetClassName}
|
||||||
|
data-workspace-sheet-id={sheetId}
|
||||||
|
visible={visible}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
placement={placement}
|
||||||
|
width={placement === 'left' || placement === 'right' ? width : undefined}
|
||||||
|
height={placement === 'top' || placement === 'bottom' ? height : undefined}
|
||||||
|
title={<div className="v2-workspace-config-title">
|
||||||
|
<span><strong>{title}</strong><small>{description}</small></span>
|
||||||
|
{badge ? <Tag color={badgeColor} type="light" size="small">{badge}</Tag> : null}
|
||||||
|
</div>}
|
||||||
|
onCancel={onCancel}
|
||||||
|
footer={hasFooter ? <div className="v2-workspace-config-footer">
|
||||||
|
<span>{footerNote}</span>
|
||||||
|
<div>
|
||||||
|
{secondaryActions.map((action) => <Button
|
||||||
|
key={action.label}
|
||||||
|
theme={action.theme ?? 'light'}
|
||||||
|
type={action.type}
|
||||||
|
disabled={action.disabled}
|
||||||
|
loading={action.loading}
|
||||||
|
onClick={action.onClick}
|
||||||
|
>{action.label}</Button>)}
|
||||||
|
{primaryAction ? <Button
|
||||||
|
theme={primaryAction.theme ?? 'solid'}
|
||||||
|
type={primaryAction.type ?? 'primary'}
|
||||||
|
disabled={primaryAction.disabled}
|
||||||
|
loading={primaryAction.loading}
|
||||||
|
onClick={primaryAction.onClick}
|
||||||
|
>{primaryAction.label}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div> : null}
|
||||||
|
>
|
||||||
|
<div className="v2-workspace-config-body">
|
||||||
|
{summaryItems.length ? <div className="v2-workspace-config-summary" role="list" aria-label={`${ariaLabel}摘要`}>
|
||||||
|
{summaryItems.map((item) => <span className={`is-${item.tone ?? 'neutral'}`} role="listitem" key={item.label}>
|
||||||
|
<small>{item.label}</small>
|
||||||
|
<strong>{item.value}</strong>
|
||||||
|
{item.detail ? <em>{item.detail}</em> : null}
|
||||||
|
</span>)}
|
||||||
|
</div> : null}
|
||||||
|
<div className="v2-workspace-config-content">{children}</div>
|
||||||
|
</div>
|
||||||
|
</SideSheet>;
|
||||||
|
}
|
||||||
@@ -20594,3 +20594,368 @@
|
|||||||
min-height: 36px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user