feat(platform): add customer primary navigation path

This commit is contained in:
lingniu
2026-07-05 17:57:37 +08:00
parent 680006e36f
commit 3be803316a
3 changed files with 91 additions and 0 deletions

View File

@@ -67,6 +67,14 @@ const navGroups = [
} }
]; ];
const customerPrimaryPath = [
{ label: '车辆地图', page: 'map' as const },
{ label: '轨迹回放', page: 'history' as const },
{ label: '统计查询', page: 'mileage' as const },
{ label: '数据导出', page: 'history-query' as const },
{ label: '告警通知', page: 'alert-events' as const }
];
function linkHealthClassName(count: number | null) { function linkHealthClassName(count: number | null) {
if (count == null) { if (count == null) {
return ''; return '';
@@ -167,6 +175,25 @@ export function AppShell({
<span> / / </span> <span> / / </span>
</span> </span>
</div> </div>
<div className="vp-customer-primary-path" aria-label="客户主线">
<div className="vp-customer-primary-path-head">
<strong>线</strong>
<span></span>
</div>
<div className="vp-customer-primary-path-actions">
{customerPrimaryPath.map((item) => (
<button
key={item.page}
type="button"
className={selectedPage === item.page ? 'vp-customer-primary-path-action vp-customer-primary-path-action-active' : 'vp-customer-primary-path-action'}
onClick={() => onChange(item.page)}
aria-label={`客户主线 ${item.label}`}
>
{item.label}
</button>
))}
</div>
</div>
<div className="vp-nav-groups"> <div className="vp-nav-groups">
{navGroups.map((group) => ( {navGroups.map((group) => (
<div key={group.title} className="vp-nav-group"> <div key={group.title} className="vp-nav-group">

View File

@@ -69,6 +69,63 @@ body {
white-space: nowrap; white-space: nowrap;
} }
.vp-customer-primary-path {
margin: 0 4px 14px;
padding: 12px;
border: 1px solid rgba(22, 100, 255, 0.16);
border-radius: 8px;
background: #f8fbff;
display: grid;
gap: 10px;
}
.vp-customer-primary-path-head {
display: grid;
gap: 3px;
}
.vp-customer-primary-path-head strong {
color: var(--vp-text);
font-size: 13px;
line-height: 18px;
}
.vp-customer-primary-path-head span {
color: var(--vp-text-muted);
font-size: 11px;
line-height: 16px;
}
.vp-customer-primary-path-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
}
.vp-customer-primary-path-action {
min-height: 30px;
padding: 5px 8px;
border: 1px solid var(--vp-border);
border-radius: 7px;
background: #ffffff;
color: var(--vp-text-muted);
cursor: pointer;
font: inherit;
font-size: 12px;
font-weight: 600;
line-height: 18px;
text-align: center;
}
.vp-customer-primary-path-action:hover,
.vp-customer-primary-path-action:focus-visible,
.vp-customer-primary-path-action-active {
border-color: rgba(22, 100, 255, 0.38);
background: #ffffff;
color: var(--vp-primary);
outline: none;
}
.vp-nav-groups { .vp-nav-groups {
display: grid; display: grid;
gap: 12px; gap: 12px;

View File

@@ -22,6 +22,13 @@ test('renders vehicle platform shell', () => {
render(<App />); render(<App />);
expect(screen.getByText('车辆服务中台')).toBeInTheDocument(); expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
expect(screen.getByText('实时地图 / 轨迹统计 / 告警通知')).toBeInTheDocument(); expect(screen.getByText('实时地图 / 轨迹统计 / 告警通知')).toBeInTheDocument();
expect(screen.getByText('客户主线')).toBeInTheDocument();
expect(screen.getByText('先看车,再查时间窗,最后导出和通知。')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 车辆地图' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 轨迹回放' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 统计查询' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 数据导出' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 告警通知' })).toBeInTheDocument();
expect(screen.getByText('实时运营')).toBeInTheDocument(); expect(screen.getByText('实时运营')).toBeInTheDocument();
expect(screen.getByText('地图看车、在线监控、异常优先')).toBeInTheDocument(); expect(screen.getByText('地图看车、在线监控、异常优先')).toBeInTheDocument();
expect(screen.getAllByText('车辆服务').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('车辆服务').length).toBeGreaterThanOrEqual(1);