feat(platform): add vehicle pool quick views
This commit is contained in:
@@ -782,6 +782,49 @@ export function Vehicles({
|
||||
onClick: copyCustomerServicePackage
|
||||
}
|
||||
];
|
||||
const offlineVehicleCount = Math.max((summary?.totalVehicles ?? pagination.total) - (summary?.onlineVehicles ?? 0), 0);
|
||||
const vehiclePoolQuickViews = [
|
||||
{
|
||||
title: '在线运营',
|
||||
value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`,
|
||||
detail: '进入地图查看在线车辆分布、最近位置和运营范围。',
|
||||
action: '打开地图',
|
||||
color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const,
|
||||
onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' })
|
||||
},
|
||||
{
|
||||
title: '离线断链',
|
||||
value: `${offlineVehicleCount.toLocaleString()} 离线`,
|
||||
detail: '筛出离线车辆,结合最后上报时间判断断链影响。',
|
||||
action: '排查离线',
|
||||
color: offlineVehicleCount > 0 ? 'orange' as const : 'green' as const,
|
||||
onClick: () => applyFilters({ ...filters, online: 'offline' })
|
||||
},
|
||||
{
|
||||
title: '无来源车辆',
|
||||
value: `${(summary?.noDataVehicles ?? 0).toLocaleString()} 无来源`,
|
||||
detail: '优先确认平台转发、订阅和车辆身份是否可归并。',
|
||||
action: '平台转发',
|
||||
color: (summary?.noDataVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
|
||||
onClick: () => applyFilters({ ...filters, serviceStatus: 'no_data' })
|
||||
},
|
||||
{
|
||||
title: '身份维护',
|
||||
value: `${(summary?.unboundVehicles ?? 0).toLocaleString()} 未绑定`,
|
||||
detail: '补齐车牌、手机号与 VIN 关系,保证客户能按车辆查数据。',
|
||||
action: '维护绑定',
|
||||
color: (summary?.unboundVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
|
||||
onClick: () => applyFilters({ ...filters, bindingStatus: 'unbound' })
|
||||
},
|
||||
{
|
||||
title: '报表交付',
|
||||
value: `${rows.length.toLocaleString()} 当前页`,
|
||||
detail: '导出当前车辆池清单,作为客户报表和后续轨迹统计入口。',
|
||||
action: '导出清单',
|
||||
color: rows.length > 0 ? 'blue' as const : 'grey' as const,
|
||||
onClick: exportVehicles
|
||||
}
|
||||
];
|
||||
const vehicleDecisionItems = [
|
||||
{
|
||||
title: '在线车辆',
|
||||
@@ -1270,6 +1313,32 @@ export function Vehicles({
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="vp-vehicle-pool-quick-views" aria-label="车辆池快捷视图">
|
||||
<div className="vp-vehicle-pool-quick-copy">
|
||||
<Space wrap>
|
||||
<Tag color="blue">车辆池快捷视图</Tag>
|
||||
<Tag color={filters.keyword?.trim() ? 'green' : 'grey'}>{filters.keyword?.trim() ? '单车范围' : '车辆池'}</Tag>
|
||||
</Space>
|
||||
<strong>按客户服务场景一键切换车辆池:在线运营、离线断链、无来源、身份维护和报表交付。</strong>
|
||||
<span>业务人员先按问题选择车辆池,再进入地图、实时、轨迹、统计或导出;协议来源继续沉到证据层。</span>
|
||||
</div>
|
||||
<div className="vp-vehicle-pool-quick-grid">
|
||||
{vehiclePoolQuickViews.map((item) => (
|
||||
<button
|
||||
key={item.title}
|
||||
type="button"
|
||||
className="vp-vehicle-pool-quick-item"
|
||||
onClick={item.onClick}
|
||||
aria-label={`车辆池快捷视图 ${item.title} ${item.value} ${item.action}`}
|
||||
>
|
||||
<Tag color={item.color}>{item.title}</Tag>
|
||||
<strong>{item.value}</strong>
|
||||
<span>{item.detail}</span>
|
||||
<em>{item.action}</em>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<Card bordered title="车辆服务台">
|
||||
<div className="vp-vehicle-service-desk">
|
||||
<div className="vp-vehicle-service-summary">
|
||||
|
||||
@@ -4877,6 +4877,92 @@ button.vp-realtime-command-item:focus-visible {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-views {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid rgba(255, 136, 0, 0.18);
|
||||
border-radius: var(--vp-radius);
|
||||
background: #ffffff;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 0.55fr) minmax(0, 1.45fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-copy {
|
||||
padding: 18px;
|
||||
border-right: 1px solid var(--vp-border);
|
||||
background: #fffaf3;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-copy strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-copy span {
|
||||
color: var(--vp-text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-grid {
|
||||
padding: 14px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-item {
|
||||
min-height: 142px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: var(--vp-radius);
|
||||
background: #fffefd;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-item:hover,
|
||||
.vp-vehicle-pool-quick-item:focus-visible {
|
||||
border-color: rgba(255, 136, 0, 0.42);
|
||||
background: #fffaf3;
|
||||
box-shadow: 0 0 0 3px rgba(255, 136, 0, 0.08);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-item strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 26px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-item span {
|
||||
color: var(--vp-text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.vp-vehicle-pool-quick-item em {
|
||||
color: var(--vp-primary);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-vehicle-lookup-strip {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
@@ -11828,6 +11914,8 @@ button.vp-realtime-command-item:focus-visible {
|
||||
.vp-alert-closure-steps,
|
||||
.vp-vehicle-asset-console,
|
||||
.vp-vehicle-asset-console-grid,
|
||||
.vp-vehicle-pool-quick-views,
|
||||
.vp-vehicle-pool-quick-grid,
|
||||
.vp-vehicle-service-desk,
|
||||
.vp-vehicle-service-grid,
|
||||
.vp-vehicle-lookup-head,
|
||||
|
||||
@@ -2682,6 +2682,13 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
|
||||
expect(screen.getByRole('button', { name: '车辆资产运营台 在线车辆 73 在线 打开实时' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆资产运营台 待处理 13 待处理 异常优先' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆资产运营台 服务交付 2 当前页 复制交付包' })).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆池快捷视图')).toBeInTheDocument();
|
||||
expect(screen.getByText('按客户服务场景一键切换车辆池:在线运营、离线断链、无来源、身份维护和报表交付。')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆池快捷视图 在线运营 73 在线 打开地图' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆池快捷视图 离线断链 108 离线 排查离线' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆池快捷视图 无来源车辆 4 无来源 平台转发' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆池快捷视图 身份维护 9 未绑定 维护绑定' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆池快捷视图 报表交付 2 当前页 导出清单' })).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆服务台')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户视角')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户查车路径')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user