feat(platform): add customer vehicle lookup path
This commit is contained in:
@@ -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>客户侧只关心车辆服务结果,32960、808、MQTT 作为证据来源参与筛选和可信度判断。</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) => (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user