feat(platform): add vehicle dispatch checklist
This commit is contained in:
@@ -7,6 +7,7 @@ import { DataEmpty } from '../components/DataEmpty';
|
|||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
import { SourceStatusTags } from '../components/SourceStatusTags';
|
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||||
import { StatusTag } from '../components/StatusTag';
|
import { StatusTag } from '../components/StatusTag';
|
||||||
|
import { buildAppHash } from '../domain/appRoute';
|
||||||
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||||
import { summarizeVehicleService, type VehicleServiceVerdict } from '../domain/vehicleService';
|
import { summarizeVehicleService, type VehicleServiceVerdict } from '../domain/vehicleService';
|
||||||
|
|
||||||
@@ -192,6 +193,48 @@ function vehicleGovernanceSummaryText({
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appURL(hash: string) {
|
||||||
|
return `${window.location.origin}${window.location.pathname}${hash}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vehicleDispatchListText({
|
||||||
|
filters,
|
||||||
|
rows,
|
||||||
|
total
|
||||||
|
}: {
|
||||||
|
filters: Record<string, string>;
|
||||||
|
rows: VehicleCoverageRow[];
|
||||||
|
total: number;
|
||||||
|
}) {
|
||||||
|
const lines = [
|
||||||
|
'【车辆处置清单】',
|
||||||
|
`当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`,
|
||||||
|
`当前页:${rows.length.toLocaleString()} 辆 / 总计 ${total.toLocaleString()} 辆`,
|
||||||
|
''
|
||||||
|
];
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
const action = vehicleActionRecommendation(row);
|
||||||
|
const status = vehicleServiceStatus(row);
|
||||||
|
const protocol = primaryRowProtocol(row, filters.protocol);
|
||||||
|
const identity = [
|
||||||
|
row.plate ? `车牌 ${row.plate}` : '车牌 -',
|
||||||
|
row.vin ? `VIN ${row.vin}` : 'VIN -',
|
||||||
|
row.phone ? `手机号 ${row.phone}` : '手机号 -',
|
||||||
|
row.oem ? `OEM ${row.oem}` : 'OEM -'
|
||||||
|
].join(' / ');
|
||||||
|
lines.push(
|
||||||
|
`${index + 1}. ${identity}`,
|
||||||
|
` 状态:${status.label};在线:${row.online ? '在线' : '离线'};来源:${sourceDiagnosisText(row)};最后时间:${row.lastSeen || '-'}`,
|
||||||
|
` 建议动作:${action.label}`,
|
||||||
|
` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol }))}`,
|
||||||
|
` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol }))}`,
|
||||||
|
` 轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol }))}`,
|
||||||
|
` 告警事件:${appURL(buildAppHash({ page: 'alert-events', keyword: row.vin, protocol }))}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
type VehicleActionRecommendation = {
|
type VehicleActionRecommendation = {
|
||||||
label: string;
|
label: string;
|
||||||
color: 'green' | 'orange' | 'red';
|
color: 'green' | 'orange' | 'red';
|
||||||
@@ -451,6 +494,13 @@ export function Vehicles({
|
|||||||
const copyGovernanceSummary = () => {
|
const copyGovernanceSummary = () => {
|
||||||
copyText(vehicleGovernanceSummaryText({ filters, summary, actionQueue }), '治理摘要');
|
copyText(vehicleGovernanceSummaryText({ filters, summary, actionQueue }), '治理摘要');
|
||||||
};
|
};
|
||||||
|
const copyDispatchList = () => {
|
||||||
|
if (rows.length === 0) {
|
||||||
|
Toast.warning('当前没有可复制的车辆处置清单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
copyText(vehicleDispatchListText({ filters, rows, total: pagination.total }), '车辆处置清单');
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilters(initialFilters);
|
setFilters(initialFilters);
|
||||||
@@ -628,6 +678,7 @@ export function Vehicles({
|
|||||||
<Space>
|
<Space>
|
||||||
<span>当前车辆结果</span>
|
<span>当前车辆结果</span>
|
||||||
<Button size="small" theme="light" onClick={copyGovernanceSummary}>复制治理摘要</Button>
|
<Button size="small" theme="light" onClick={copyGovernanceSummary}>复制治理摘要</Button>
|
||||||
|
<Button size="small" theme="light" onClick={copyDispatchList}>复制当前页处置清单</Button>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('renders vehicle platform shell', () => {
|
test('renders vehicle platform shell', () => {
|
||||||
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: { items: [], total: 0, limit: 20, offset: 0, linkHealth: [], runtime: { requestTimeoutMs: 5000 } },
|
||||||
|
traceId: 'trace-test',
|
||||||
|
timestamp: 1783094400000
|
||||||
|
})
|
||||||
|
}) as Response);
|
||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
|
expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
|
||||||
expect(screen.getByText('一车一服务 / 多源归并')).toBeInTheDocument();
|
expect(screen.getByText('一车一服务 / 多源归并')).toBeInTheDocument();
|
||||||
@@ -125,7 +134,7 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
expect(await screen.findByText('地图就绪')).toBeInTheDocument();
|
expect(await screen.findByText('地图就绪')).toBeInTheDocument();
|
||||||
expect(screen.getByText((content) => content.includes('platform-20260704153357'))).toBeInTheDocument();
|
expect(await screen.findByText((content) => content.includes('platform-20260704153357'))).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole('button', { name: '顶部地图态势' }));
|
fireEvent.click(screen.getByRole('button', { name: '顶部地图态势' }));
|
||||||
expect(window.location.hash).toBe('#/map');
|
expect(window.location.hash).toBe('#/map');
|
||||||
expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument();
|
expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument();
|
||||||
@@ -1749,6 +1758,19 @@ test('copies vehicle governance summary from vehicle center', async () => {
|
|||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('处置队列:'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('处置队列:'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('[P0] 维护身份绑定 4'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('[P0] 维护身份绑定 4'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('[P1] 补齐 YUTONG_MQTT 来源 8'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('[P1] 补齐 YUTONG_MQTT 来源 8'));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '复制当前页处置清单' }));
|
||||||
|
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆处置清单】'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前筛选:缺失来源:YUTONG_MQTT'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前页:1 辆 / 总计 12 辆'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 车牌 粤A治理1 / VIN VIN-GOV-001 / 手机号 - / OEM G7s'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('状态:来源不完整;在线:在线;来源:1/1 来源在线,缺 YUTONG_MQTT;最后时间:2026-07-03 20:12:10'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('建议动作:补齐 YUTONG_MQTT 来源'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-GOV-001&protocol=JT808'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时监控:http://localhost:3000/#/realtime?keyword=VIN-GOV-001&protocol=JT808'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹回放:http://localhost:3000/#/history?keyword=VIN-GOV-001&protocol=JT808'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警事件:http://localhost:3000/#/alert-events?keyword=VIN-GOV-001&protocol=JT808'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('filters vehicle list from recommended action', async () => {
|
test('filters vehicle list from recommended action', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user