feat(platform): add vehicle service delivery strip
This commit is contained in:
@@ -188,6 +188,8 @@ export function AppShell({
|
||||
const dateFrom = customerTimeWindow?.dateFrom ?? '';
|
||||
const dateTo = customerTimeWindow?.dateTo ?? '';
|
||||
const savedViews = customerViews ?? [];
|
||||
const serviceObjectText = currentVehicleLabel ? `车辆:${currentVehicleLabel}` : '未选择车辆';
|
||||
const serviceTimeText = dateFrom || dateTo ? `${dateFrom || '未指定'} 至 ${dateTo || '未指定'}` : '实时范围';
|
||||
|
||||
const applyTimeWindowPreset = (days: number) => {
|
||||
onCustomerTimeWindowChange?.({
|
||||
@@ -214,6 +216,7 @@ export function AppShell({
|
||||
{ label: '导出', ariaLabel: '顶部数据导出', page: 'history-query' as const },
|
||||
{ label: '告警', ariaLabel: '顶部告警事件', page: 'alert-events' as const }
|
||||
];
|
||||
const deliveryActions = quickActions.filter((item) => item.page !== 'realtime');
|
||||
|
||||
return (
|
||||
<div className="vp-app">
|
||||
@@ -299,6 +302,35 @@ export function AppShell({
|
||||
<Button theme="solid" type="primary" loading={searching} onClick={search}>查询车辆</Button>
|
||||
</Space>
|
||||
<div className="vp-topbar-context">
|
||||
<div className="vp-service-delivery-strip" aria-label="车辆服务交付条">
|
||||
<div className="vp-service-delivery-strip-head">
|
||||
<strong>车辆服务交付条</strong>
|
||||
<span>围绕一辆车或一个车队完成监控、复盘、统计、导出和通知。</span>
|
||||
</div>
|
||||
<div className="vp-service-delivery-strip-fact">
|
||||
<span>服务对象</span>
|
||||
<strong>{serviceObjectText}</strong>
|
||||
</div>
|
||||
<div className="vp-service-delivery-strip-fact">
|
||||
<span>服务时间</span>
|
||||
<strong>{serviceTimeText}</strong>
|
||||
</div>
|
||||
<div className="vp-service-delivery-strip-actions">
|
||||
<span>服务动作</span>
|
||||
<div>
|
||||
{deliveryActions.map((item) => (
|
||||
<button
|
||||
key={item.page}
|
||||
type="button"
|
||||
aria-label={`车辆服务交付条 ${item.label}`}
|
||||
onClick={() => (onCustomerTask ?? onChange)(item.page)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="vp-topbar-time-window" aria-label="客户时间窗">
|
||||
<div className="vp-topbar-time-window-copy">
|
||||
<strong>客户时间窗</strong>
|
||||
|
||||
@@ -372,6 +372,81 @@ body {
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip {
|
||||
min-width: min(100%, 560px);
|
||||
max-width: 100%;
|
||||
min-height: 44px;
|
||||
padding: 6px;
|
||||
border: 1px solid rgba(22, 100, 255, 0.18);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(138px, 0.9fr) minmax(112px, 0.65fr) minmax(150px, 0.85fr) minmax(150px, 1fr);
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-head,
|
||||
.vp-service-delivery-strip-fact,
|
||||
.vp-service-delivery-strip-actions {
|
||||
min-width: 0;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
display: grid;
|
||||
align-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-head strong,
|
||||
.vp-service-delivery-strip-fact strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-head span,
|
||||
.vp-service-delivery-strip-fact span,
|
||||
.vp-service-delivery-strip-actions > span {
|
||||
color: var(--vp-text-muted);
|
||||
font-size: 11px;
|
||||
line-height: 15px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-actions > div {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-actions button {
|
||||
height: 22px;
|
||||
min-width: 34px;
|
||||
padding: 0 6px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: #ffffff;
|
||||
color: var(--vp-primary);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-service-delivery-strip-actions button:hover,
|
||||
.vp-service-delivery-strip-actions button:focus-visible {
|
||||
background: rgba(22, 100, 255, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vp-topbar-quick-actions {
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
@@ -10942,6 +11017,7 @@ button.vp-realtime-command-item:focus-visible {
|
||||
.vp-customer-service-package,
|
||||
.vp-customer-service-grid,
|
||||
.vp-vehicle-task-grid,
|
||||
.vp-service-delivery-strip,
|
||||
.vp-dispatch-operations-home,
|
||||
.vp-dispatch-operations-highlights,
|
||||
.vp-customer-delivery-workbench,
|
||||
|
||||
@@ -52,6 +52,31 @@ test('renders vehicle platform shell', () => {
|
||||
expect(screen.getAllByText('运营总览').length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('topbar frames customer work as a vehicle service delivery strip', () => {
|
||||
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 />);
|
||||
|
||||
expect(screen.getByText('车辆服务交付条')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务对象')).toBeInTheDocument();
|
||||
expect(screen.getByText('未选择车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务时间')).toBeInTheDocument();
|
||||
expect(screen.getByText('实时范围')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务动作')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务交付条 地图' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务交付条 轨迹' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务交付条 统计' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务交付条 导出' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务交付条 告警' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('exposes AMap operations shortcuts when map key is configured', async () => {
|
||||
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
|
||||
Reference in New Issue
Block a user