客户时间窗
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index 35176b09..71493cb5 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -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,
diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx
index a568ac76..dc2639da 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -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(
);
+
+ 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) => {