diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index 32e1fed5..b78389b6 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -4661,6 +4661,97 @@ button.vp-realtime-command-item:focus-visible {
gap: 12px;
}
+.vp-alert-customer-service-desk {
+ margin-top: 16px;
+ margin-bottom: 16px;
+ border: 1px solid rgba(239, 68, 68, 0.18);
+ border-radius: var(--vp-radius);
+ background: #fff;
+ display: grid;
+ grid-template-columns: minmax(300px, 0.62fr) minmax(0, 1.38fr);
+ overflow: hidden;
+ box-shadow: var(--vp-shadow-sm);
+}
+
+.vp-alert-customer-service-summary {
+ padding: 18px;
+ border-right: 1px solid var(--vp-border);
+ background: #fff7f7;
+ display: grid;
+ gap: 12px;
+ align-content: start;
+}
+
+.vp-alert-customer-service-summary strong {
+ color: var(--vp-text);
+ font-size: 18px;
+ line-height: 26px;
+ word-break: break-word;
+}
+
+.vp-alert-customer-service-summary span {
+ color: var(--vp-text-muted);
+ font-size: 13px;
+ line-height: 20px;
+}
+
+.vp-alert-customer-service-grid {
+ padding: 16px;
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.vp-alert-customer-service-item {
+ min-height: 144px;
+ padding: 12px;
+ border: 1px solid rgba(239, 68, 68, 0.14);
+ border-radius: 8px;
+ background: #fbfcff;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ text-align: left;
+ display: grid;
+ gap: 8px;
+ align-content: start;
+}
+
+.vp-alert-customer-service-item:hover,
+.vp-alert-customer-service-item:focus-visible {
+ border-color: rgba(239, 68, 68, 0.4);
+ background: #fff7f7;
+ box-shadow: var(--vp-shadow-sm);
+ outline: none;
+}
+
+.vp-alert-customer-service-item:disabled {
+ cursor: not-allowed;
+ opacity: 0.62;
+}
+
+.vp-alert-customer-service-item strong {
+ color: var(--vp-text);
+ font-size: 18px;
+ line-height: 24px;
+ word-break: break-word;
+}
+
+.vp-alert-customer-service-item span {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+ word-break: break-word;
+}
+
+.vp-alert-customer-service-item em {
+ color: #dc2626;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 18px;
+}
+
.vp-alert-decision-board {
display: grid;
grid-template-columns: minmax(280px, 0.74fr) minmax(0, 1.26fr);
@@ -9446,6 +9537,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-conclusion-grid,
.vp-monitor-layout,
.vp-alert-flow,
+ .vp-alert-customer-service-desk,
+ .vp-alert-customer-service-grid,
.vp-alert-decision-board,
.vp-alert-decision-grid,
.vp-alert-sla-board,
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 de45946b..9f2997e2 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -3887,7 +3887,7 @@ test('shows alert rule and notification policy workspace on quality page', async
expect(screen.getByText('客户关心的是哪些车辆受影响、是否影响实时/轨迹/里程、是否需要通知、何时恢复;协议来源只作为证据辅助判断。')).toBeInTheDocument();
expect(screen.getAllByText('立即处置').length).toBeGreaterThan(0);
expect(screen.getByText('待通知车辆')).toBeInTheDocument();
- expect(screen.getByText('证据闭环')).toBeInTheDocument();
+ expect(screen.getAllByText('证据闭环').length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '复制告警决策' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户告警决策说明】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('决策结论:立即处置'));
@@ -11235,6 +11235,13 @@ test('opens realtime status from quality issue row with source evidence', async
render(
);
expect(await screen.findByText('13307795425')).toBeInTheDocument();
+ expect(screen.getByText('客户告警服务台')).toBeInTheDocument();
+ expect(screen.getByText('客户先看到哪些车受影响、是否要通知、证据是否齐全、恢复后如何验收。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户告警服务台 影响车辆 1 辆 复制影响' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户告警服务台 待通知 P0 1 / P1 0 复制通知' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户告警服务台 焦点车辆 粤AG18312 / 13307795425 车辆服务' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户告警服务台 证据闭环 可复核 复制交接' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '客户告警服务台 恢复验收 有标准 复制回执' })).toBeInTheDocument();
fireEvent.click(screen.getAllByRole('button', { name: '实时状态' })[0]);
await waitFor(() => {