feat(platform): add customer task dispatch

This commit is contained in:
lingniu
2026-07-05 19:03:12 +08:00
parent 33f1ed24e4
commit e48ea7d22a
3 changed files with 103 additions and 0 deletions

View File

@@ -75,6 +75,14 @@ const customerPrimaryPath = [
{ label: '告警通知', page: 'alert-events' as const }
];
const customerTaskPath = [
{ label: '地图看车', question: '车辆在哪里', page: 'map' as const },
{ label: '轨迹回放', question: '这段时间怎么跑', page: 'history' as const },
{ label: '统计查询', question: '里程怎么算', page: 'mileage' as const },
{ label: '数据导出', question: '证据怎么给客户', page: 'history-query' as const },
{ label: '告警通知', question: '异常谁处理', page: 'alert-events' as const }
];
function linkHealthClassName(count: number | null) {
if (count == null) {
return '';
@@ -193,6 +201,26 @@ export function AppShell({
</button>
))}
</div>
<div className="vp-customer-task-dispatch" aria-label="客户任务分发台">
<div className="vp-customer-task-dispatch-head">
<strong></strong>
<span> VIN / / </span>
</div>
<div className="vp-customer-task-dispatch-actions">
{customerTaskPath.map((item) => (
<button
key={item.page}
type="button"
className={selectedPage === item.page ? 'vp-customer-task-dispatch-action vp-customer-task-dispatch-action-active' : 'vp-customer-task-dispatch-action'}
onClick={() => onChange(item.page)}
aria-label={`客户任务 ${item.label} ${item.question}`}
>
<strong>{item.label}</strong>
<span>{item.question}</span>
</button>
))}
</div>
</div>
</div>
<div className="vp-nav-groups">
{navGroups.map((group) => (

View File

@@ -126,6 +126,74 @@ body {
outline: none;
}
.vp-customer-task-dispatch {
padding-top: 10px;
border-top: 1px solid rgba(22, 100, 255, 0.12);
display: grid;
gap: 8px;
}
.vp-customer-task-dispatch-head {
display: grid;
gap: 3px;
}
.vp-customer-task-dispatch-head strong {
color: var(--vp-text);
font-size: 13px;
line-height: 18px;
}
.vp-customer-task-dispatch-head span {
color: var(--vp-text-muted);
font-size: 11px;
line-height: 16px;
}
.vp-customer-task-dispatch-actions {
display: grid;
gap: 6px;
}
.vp-customer-task-dispatch-action {
min-height: 42px;
padding: 7px 8px;
border: 1px solid var(--vp-border);
border-radius: 7px;
background: #fff;
color: var(--vp-text);
cursor: pointer;
font: inherit;
text-align: left;
display: grid;
gap: 1px;
}
.vp-customer-task-dispatch-action strong {
font-size: 12px;
line-height: 17px;
}
.vp-customer-task-dispatch-action span {
color: var(--vp-text-muted);
font-size: 11px;
line-height: 15px;
}
.vp-customer-task-dispatch-action:hover,
.vp-customer-task-dispatch-action:focus-visible,
.vp-customer-task-dispatch-action-active {
border-color: rgba(22, 100, 255, 0.38);
background: #ffffff;
outline: none;
}
.vp-customer-task-dispatch-action-active strong,
.vp-customer-task-dispatch-action:hover strong,
.vp-customer-task-dispatch-action:focus-visible strong {
color: var(--vp-primary);
}
.vp-nav-groups {
display: grid;
gap: 12px;

View File

@@ -24,6 +24,13 @@ test('renders vehicle platform shell', () => {
expect(screen.getByText('实时地图 / 轨迹统计 / 告警通知')).toBeInTheDocument();
expect(screen.getByText('客户主线')).toBeInTheDocument();
expect(screen.getByText('先看车,再查时间窗,最后导出和通知。')).toBeInTheDocument();
expect(screen.getByText('客户任务分发台')).toBeInTheDocument();
expect(screen.getByText('输入 VIN / 车牌 / 手机号后,地图、轨迹、统计、导出和告警都围绕同一辆车服务。')).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.getByRole('button', { name: '客户主线 车辆地图' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 轨迹回放' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户主线 统计查询' })).toBeInTheDocument();