diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx
index 6e4ea07e..347ded73 100644
--- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx
@@ -249,6 +249,10 @@ function priorityIssueRows(rows: QualityIssueRow[]): PriorityIssueRow[] {
.slice(0, 5);
}
+function priorityIssueFromRow(row: QualityIssueRow): PriorityIssueRow {
+ return priorityIssueRows([row])[0];
+}
+
function priorityIssueNotificationText(row: PriorityIssueRow) {
const lookup = qualityIssueVehicleLookup(row);
const dateFrom = issueEvidenceDate(row.lastSeen);
@@ -817,6 +821,7 @@ export function Quality({
+
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 d590e6c3..0fcc0f05 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -2708,7 +2708,7 @@ test('copies notification text from quality priority queue', async () => {
render();
expect(await screen.findByText('处置优先队列')).toBeInTheDocument();
- fireEvent.click(await screen.findByRole('button', { name: '复制通知' }));
+ fireEvent.click(screen.getAllByRole('button', { name: '复制通知' })[0]);
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【P0 告警通知】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:粤A通知1 / 13307795425'));
@@ -2912,6 +2912,93 @@ test('copies priority queue notification digest on quality page', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选:http://localhost:3000/#/quality'));
});
+test('copies notification text from regular quality issue row', async () => {
+ window.history.replaceState(null, '', '/#/quality');
+ 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')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: { linkHealth: [], kafkaLag: 0, activeConnections: 120000, capacityFindings: [], redisOnlineKeys: 368, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ if (path.includes('/api/quality/summary')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: {
+ issueVehicleCount: 1,
+ issueRecordCount: 1,
+ errorCount: 0,
+ warningCount: 1,
+ protocols: [{ name: 'GB32960', count: 1 }],
+ issueTypes: [{ name: 'FIELD_MISSING', count: 1 }]
+ },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ if (path.includes('/api/quality/issues')) {
+ return {
+ ok: true,
+ json: async () => ({
+ data: {
+ items: [{
+ vin: 'VIN-ROW-NOTIFY',
+ plate: '粤A行通知',
+ phone: '13307795425',
+ sourceEndpoint: '115.29.187.205:32960',
+ protocol: 'GB32960',
+ issueType: 'FIELD_MISSING',
+ severity: 'warning',
+ lastSeen: '2026-07-03 20:12:10',
+ detail: '位置字段缺失,影响轨迹回放'
+ }],
+ total: 1,
+ limit: 20,
+ offset: 0
+ },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ }
+ return {
+ ok: true,
+ json: async () => ({
+ data: { items: [], total: 0, limit: 20, offset: 0 },
+ traceId: 'trace-test',
+ timestamp: 1783094400000
+ })
+ } as Response;
+ });
+
+ render();
+
+ expect(await screen.findByText('质量问题')).toBeInTheDocument();
+ const copyButtons = screen.getAllByRole('button', { name: '复制通知' });
+ expect(copyButtons.length).toBeGreaterThanOrEqual(2);
+ fireEvent.click(copyButtons[copyButtons.length - 1]);
+
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【P1 告警通知】字段缺失'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:粤A行通知 / VIN-ROW-NOTIFY'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据来源:GB32960'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('建议动作:核对解析字段'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('详情:位置字段缺失,影响轨迹回放'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据:http://localhost:3000/#/history?keyword=VIN-ROW-NOTIFY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-ROW-NOTIFY&protocol=GB32960'));
+});
+
test('opens realtime map context from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {