feat(platform): add customer vehicle lookup path

This commit is contained in:
lingniu
2026-07-05 03:25:14 +08:00
parent 46d2421511
commit 3097fd59eb
3 changed files with 167 additions and 0 deletions

View File

@@ -273,6 +273,43 @@ function vehicleIdentityMaintenanceText({
return lines.join('\n');
}
function vehicleCustomerLookupText({
filters,
rows,
total,
summary
}: {
filters: Record<string, string>;
rows: VehicleCoverageRow[];
total: number;
summary: VehicleCoverageSummary | null;
}) {
const serviceMode = filters.keyword?.trim() ? '单车服务' : '车辆池';
const scopeHash = buildAppHash({ page: 'vehicles', keyword: filters.keyword, protocol: filters.protocol, filters });
const singleVehicleFilters = filters.keyword?.trim()
? { keyword: filters.keyword.trim(), ...(filters.protocol ? { protocol: filters.protocol } : {}) }
: {};
const mapFilters = { ...filters, online: filters.online || 'online' };
const lines = [
'【客户查车路径】',
`服务模式:${serviceMode}`,
`当前筛选:${vehicleFilterSummary(filters).join('') || '全部车辆'}`,
`当前页:${rows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()}`,
`在线车辆:${(summary?.onlineVehicles ?? 0).toLocaleString()};待绑定:${(summary?.unboundVehicles ?? 0).toLocaleString()};暂无来源:${(summary?.noDataVehicles ?? 0).toLocaleString()};档案不完整:${(summary?.archiveIncompleteVehicles ?? 0).toLocaleString()}`,
'',
serviceMode === '单车服务'
? '推荐路径:先看车辆档案和实时监控,再按需要进入轨迹回放、里程统计、历史数据导出。'
: '推荐路径:先筛在线/离线/无来源/待绑定车辆,再进入地图态势或导出处置清单。',
`车辆服务:${appURL(scopeHash)}`,
`实时监控:${appURL(buildAppHash({ page: 'realtime', ...singleVehicleFilters }))}`,
`车辆地图:${appURL(buildAppHash({ page: 'map', keyword: filters.keyword, protocol: filters.protocol, filters: mapFilters }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'location' } }))}`,
`里程统计:${appURL(buildAppHash({ page: 'mileage', ...singleVehicleFilters }))}`,
`历史数据:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'raw', includeFields: 'true' } }))}`
];
return lines.join('\n');
}
type VehicleActionRecommendation = {
label: string;
color: 'green' | 'orange' | 'red';
@@ -557,6 +594,31 @@ export function Vehicles({
}
copyText(vehicleIdentityMaintenanceText({ filters, rows, total: pagination.total }), '车辆身份维护清单');
};
const copyCustomerLookupPath = () => {
copyText(vehicleCustomerLookupText({ filters, rows, total: pagination.total, summary }), '客户查车路径');
};
const customerLookupSteps = [
{
label: '车辆身份',
value: filters.keyword?.trim() || 'VIN / 车牌 / 手机号',
detail: '确认车辆归属、车牌、手机号和 OEM客户问题先落到一辆车。'
},
{
label: '在线状态',
value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`,
detail: '判断车辆是否仍在上报,离线车辆进入断链和来源证据排查。'
},
{
label: '轨迹与位置',
value: filters.keyword?.trim() ? '可回放' : '先选车辆',
detail: '单车进入轨迹回放,车辆池进入地图态势查看位置分布。'
},
{
label: '数据交付',
value: `${rows.length.toLocaleString()} 当前页`,
detail: '导出车辆清单再进入历史页交付轨迹、RAW 和字段证据。'
}
];
const serviceDeskActions = useMemo(() => [
{
label: '实时监控',
@@ -789,6 +851,34 @@ export function Vehicles({
</div>
</div>
</Card>
<Card bordered title="客户查车路径" style={{ marginTop: 16 }}>
<div className="vp-vehicle-lookup-strip">
<div className="vp-vehicle-lookup-head">
<div>
<Space wrap>
<Tag color={filters.keyword?.trim() ? 'green' : 'blue'}>{filters.keyword?.trim() ? '单车服务' : '车辆池服务'}</Tag>
<Tag color="grey">{vehicleFilterSummary(filters).join(' / ') || '全部车辆'}</Tag>
</Space>
<strong>{filters.keyword?.trim() ? '围绕一辆车完成监控、回放、统计和导出' : '从车辆池筛出客户要看的车,再进入单车服务'}</strong>
<span>32960808MQTT </span>
</div>
<Space wrap>
<Button size="small" theme="solid" type="primary" disabled={!onOpenMap} onClick={() => onOpenMap?.({ ...serviceScopeFilters, online: 'online' })}></Button>
<Button size="small" disabled={!onOpenHistory || !filters.keyword?.trim()} onClick={() => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' })}></Button>
<Button size="small" icon={<IconCopy />} onClick={copyCustomerLookupPath}></Button>
</Space>
</div>
<div className="vp-vehicle-lookup-grid">
{customerLookupSteps.map((step) => (
<div key={step.label} className="vp-vehicle-lookup-item">
<Tag color="blue">{step.label}</Tag>
<strong>{step.value}</strong>
<span>{step.detail}</span>
</div>
))}
</div>
</div>
</Card>
<Card bordered title="车辆服务决策板" style={{ marginTop: 16 }}>
<div className="vp-vehicle-decision-grid">
{vehicleDecisionItems.map((item) => (

View File

@@ -1003,6 +1003,72 @@ button.vp-realtime-command-item:focus-visible {
line-height: 20px;
}
.vp-vehicle-lookup-strip {
display: grid;
gap: 14px;
}
.vp-vehicle-lookup-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 14px;
border: 1px solid rgba(22, 100, 255, 0.16);
border-radius: 8px;
background: #f6f9ff;
}
.vp-vehicle-lookup-head > div {
min-width: 0;
display: grid;
gap: 8px;
}
.vp-vehicle-lookup-head strong {
color: var(--vp-text);
font-size: 18px;
line-height: 24px;
font-weight: 700;
}
.vp-vehicle-lookup-head span {
color: var(--vp-text-muted);
font-size: 13px;
line-height: 20px;
}
.vp-vehicle-lookup-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.vp-vehicle-lookup-item {
min-height: 128px;
padding: 12px;
border: 1px solid var(--vp-border);
border-radius: 8px;
background: #fbfcff;
display: grid;
gap: 8px;
align-content: start;
}
.vp-vehicle-lookup-item strong {
color: var(--vp-text);
font-size: 18px;
line-height: 24px;
font-weight: 700;
word-break: break-word;
}
.vp-vehicle-lookup-item span {
color: var(--vp-text-muted);
font-size: 13px;
line-height: 20px;
}
.vp-vehicle-decision-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -4035,6 +4101,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-current-service-grid,
.vp-vehicle-service-desk,
.vp-vehicle-service-grid,
.vp-vehicle-lookup-head,
.vp-vehicle-lookup-grid,
.vp-vehicle-decision-grid,
.vp-time-review-package,
.vp-time-review-grid,

View File

@@ -2007,6 +2007,15 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
expect(await screen.findByText('当前车辆结果')).toBeInTheDocument();
expect(screen.getByText('车辆服务台')).toBeInTheDocument();
expect(screen.getByText('客户视角')).toBeInTheDocument();
expect(screen.getByText('客户查车路径')).toBeInTheDocument();
expect(screen.getByText('车辆池服务')).toBeInTheDocument();
expect(screen.getByText('从车辆池筛出客户要看的车,再进入单车服务')).toBeInTheDocument();
expect(screen.getByText('客户侧只关心车辆服务结果32960、808、MQTT 作为证据来源参与筛选和可信度判断。')).toBeInTheDocument();
expect(screen.getByText('车辆身份')).toBeInTheDocument();
expect(screen.getByText('VIN / 车牌 / 手机号')).toBeInTheDocument();
expect(screen.getByText('轨迹与位置')).toBeInTheDocument();
expect(screen.getAllByText('数据交付').length).toBeGreaterThanOrEqual(1);
expect(screen.getByRole('button', { name: /复制客户查车路径/ })).toBeInTheDocument();
expect(screen.getByText('车辆服务决策板')).toBeInTheDocument();
expect(screen.getAllByText('异常车辆').length).toBeGreaterThan(0);
expect(screen.getByText('4 无来源')).toBeInTheDocument();