feat(platform): add customer time window job command
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
|
||||
import { IconCopy } from '@douyinfe/semi-icons';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { DashboardSummary, OpsHealth, QualityIssueRow, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||
@@ -1186,6 +1187,58 @@ export function Dashboard({
|
||||
onClick: () => copyTimeMonitorReviewPackage()
|
||||
}
|
||||
];
|
||||
const copyTimeWindowSlaJob = () => {
|
||||
const scope = timeMonitorScope();
|
||||
const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' };
|
||||
const lines = [
|
||||
'【客户时间窗复盘作业单】',
|
||||
`作业范围:${timeMonitorSummary}`,
|
||||
`交付SLA:${timeMonitorWindowLabel}内完成轨迹复盘、统计核对、历史导出和告警说明`,
|
||||
`交付状态:${timeMonitorDeliveryStatus.label};${timeMonitorDeliveryStatus.detail}`,
|
||||
'交付物:轨迹回放链接 / 统计查询链接 / 历史导出链接 / 告警复盘链接',
|
||||
`升级条件:${formatCount(summary?.issueVehicles)} 告警车辆需要先进入告警复盘`,
|
||||
`责任动作:${timeMonitorHasAlerts ? '先处理告警,再交付证据' : '先完成客户复盘,再按需导出证据'}`,
|
||||
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
|
||||
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
|
||||
`历史导出:${appURL(buildAppHash({ page: 'history-query', keyword: scope.keyword, protocol: scope.protocol, filters: rawFilters }))}`,
|
||||
`告警复盘:${appURL(buildAppHash({ page: 'alert-events', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`
|
||||
];
|
||||
copyText(lines.join('\n'), '时间窗作业单');
|
||||
};
|
||||
const timeWindowSlaItems = [
|
||||
{
|
||||
label: '责任动作',
|
||||
value: '客户复盘',
|
||||
detail: timeMonitorHasAlerts ? '存在告警车辆,先完成告警说明和通知闭环。' : '围绕同一车辆和时间窗完成客户复盘。',
|
||||
action: '复制作业',
|
||||
color: timeMonitorHasAlerts ? 'orange' as const : 'blue' as const,
|
||||
onClick: copyTimeWindowSlaJob
|
||||
},
|
||||
{
|
||||
label: '交付时限',
|
||||
value: timeMonitorWindowLabel,
|
||||
detail: '在该时间窗内完成轨迹、统计、历史证据和异常说明。',
|
||||
action: '轨迹复盘',
|
||||
color: timeMonitorHasRange ? 'green' as const : 'orange' as const,
|
||||
onClick: openTimeMonitorHistory
|
||||
},
|
||||
{
|
||||
label: '交付物',
|
||||
value: '轨迹/统计/导出/告警',
|
||||
detail: '交付轨迹回放、统计查询、历史导出和告警复盘四个入口。',
|
||||
action: '导出证据',
|
||||
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
|
||||
onClick: openTimeMonitorRaw
|
||||
},
|
||||
{
|
||||
label: '升级条件',
|
||||
value: `${formatCount(summary?.issueVehicles)} 告警`,
|
||||
detail: timeMonitorHasAlerts ? '存在告警车辆,交付前先进入告警复盘。' : '无高风险时按常规复盘交付。',
|
||||
action: '告警复盘',
|
||||
color: timeMonitorHasAlerts ? 'orange' as const : 'green' as const,
|
||||
onClick: openTimeMonitorAlerts
|
||||
}
|
||||
];
|
||||
const dedicatedTimeMonitorActions = [
|
||||
{
|
||||
label: '轨迹回放',
|
||||
@@ -2830,6 +2883,42 @@ export function Dashboard({
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="vp-time-next-action">
|
||||
<div className="vp-time-next-action-copy">
|
||||
<Space wrap>
|
||||
<Tag color="blue">时间窗复盘 SLA 作业台</Tag>
|
||||
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
|
||||
<Tag color={timeMonitorHasAlerts ? 'orange' : 'green'}>{timeMonitorHasAlerts ? `${formatCount(summary?.issueVehicles)} 告警` : '无高风险'}</Tag>
|
||||
</Space>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>
|
||||
把一个时间窗变成可交付作业:谁处理、多久交付、交付哪些证据、异常如何升级。
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
作业范围:{timeMonitorSummary};SLA:{timeMonitorWindowLabel}。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" theme="solid" type="primary" icon={<IconCopy />} onClick={copyTimeWindowSlaJob}>
|
||||
复制时间窗作业单
|
||||
</Button>
|
||||
<Button size="small" onClick={copyTimeMonitorReviewPackage}>复制复盘包</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div className="vp-time-next-action-grid">
|
||||
{timeWindowSlaItems.map((item) => (
|
||||
<button
|
||||
key={item.label}
|
||||
type="button"
|
||||
className="vp-time-next-action-item"
|
||||
onClick={item.onClick}
|
||||
aria-label={`时间窗复盘SLA ${item.label} ${item.value} ${item.action}`}
|
||||
>
|
||||
<Tag color={item.color}>{item.label}</Tag>
|
||||
<strong>{item.value}</strong>
|
||||
<span>{item.detail}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
<details className="vp-dashboard-secondary-details vp-dashboard-customer-advanced">
|
||||
|
||||
@@ -1662,6 +1662,11 @@ test('dashboard guides customer custom time window service path', async () => {
|
||||
|
||||
test('time monitor route exposes a dedicated customer time window workspace', async () => {
|
||||
window.history.replaceState(null, '', '/#/time-monitor?keyword=%E7%B2%A4A%E6%97%B6%E9%97%B4%E7%AA%97&protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-03');
|
||||
const writeText = vi.fn(() => Promise.resolve());
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: { writeText }
|
||||
});
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const path = String(input);
|
||||
if (path.includes('/api/ops/health')) {
|
||||
@@ -1743,6 +1748,21 @@ test('time monitor route exposes a dedicated customer time window workspace', as
|
||||
expect(screen.getByRole('button', { name: '时间窗监控入口 统计查询 3 天窗口' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗监控入口 历史导出 9,988 帧' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗监控入口 告警复盘 2 告警' })).toBeInTheDocument();
|
||||
expect(screen.getByText('时间窗复盘 SLA 作业台')).toBeInTheDocument();
|
||||
expect(screen.getByText('把一个时间窗变成可交付作业:谁处理、多久交付、交付哪些证据、异常如何升级。')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗复盘SLA 责任动作 客户复盘 复制作业' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗复盘SLA 交付时限 3 天窗口 轨迹复盘' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗复盘SLA 交付物 轨迹/统计/导出/告警 导出证据' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '时间窗复盘SLA 升级条件 2 告警 告警复盘' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /复制时间窗作业单/ })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /复制时间窗作业单/ }));
|
||||
await waitFor(() => {
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户时间窗复盘作业单】'));
|
||||
});
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('作业范围:粤A时间窗 / JT808 / 2026-07-01 至 2026-07-03'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('交付SLA:3 天窗口内完成轨迹复盘、统计核对、历史导出和告警说明'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('交付物:轨迹回放链接 / 统计查询链接 / 历史导出链接 / 告警复盘链接'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('升级条件:2 告警车辆需要先进入告警复盘'));
|
||||
});
|
||||
|
||||
test('dashboard exposes vehicle data center capability matrix', async () => {
|
||||
|
||||
Reference in New Issue
Block a user