diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx
index 57cc33f2..01ed7cd1 100644
--- a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx
@@ -86,7 +86,8 @@ test('loads a paginated notification page and an independent unread total on dir
expect(view.container.querySelector('.v2-alert-notification-empty.semi-empty')).toBeInTheDocument();
expect(view.container.querySelector('.v2-alert-notification-pagination')).toBeInTheDocument();
expect(screen.getByRole('combobox', { name: '每页通知数' })).toBeInTheDocument();
- expect(view.container.querySelector('.v2-alert-channel-card.semi-card')).toBeInTheDocument();
+ expect(view.container.querySelector('.v2-alert-channel-collapse.semi-collapse')).toBeInTheDocument();
+ expect(screen.getByText('3 个 · 未启用')).toBeInTheDocument();
expect(view.container.querySelector('.v2-alert-channel-descriptions.semi-descriptions')).toBeInTheDocument();
expect(mocks.alertRulesV2).not.toHaveBeenCalled();
expect(mocks.metricCatalog).not.toHaveBeenCalled();
@@ -112,6 +113,18 @@ test('ends notification mutation feedback with a visible retryable error instead
expect(screen.getByRole('button', { name: '标为已读' })).toBeEnabled();
});
+test('keeps the mobile notification title, count, and compact bulk action visible together', async () => {
+ layout.mobile = true;
+ mocks.alertNotificationsV2.mockResolvedValue({ items: [{ id: 8, eventId: 'event-8', title: '车辆告警', content: '移动端告警', severity: 'major', read: false, createdAt: '2026-07-16T04:00:00Z' }], total: 1, limit: 100, offset: 0 });
+ const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ render();
+
+ const heading = await screen.findByRole('heading', { level: 5, name: /通知列表/ });
+ await waitFor(() => expect(heading).toHaveTextContent('1 条'));
+ expect(heading.querySelector('.v2-alert-notification-total.semi-tag')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '本页已读' })).toBeInTheDocument();
+});
+
test('clears old alert rows and inspector evidence when the event scope changes', async () => {
const oldEvent = alertEvent('old-event', 'OLDVIN', '旧告警车牌');
oldEvent.actions = [{ id: 1, action: 'detect', fromStatus: '', toStatus: 'unprocessed', actor: 'alert-evaluator', note: '规则首次命中', createdAt: '2026-07-16T04:00:00Z' }];
@@ -324,6 +337,7 @@ test('keeps mobile rule selection focused and opens editing in a Semi bottom Sid
expect(document.querySelector('.v2-alert-rule-editor-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(92dvh, 820px)' });
expect(within(editor).getByDisplayValue('测试超速规则')).toBeInTheDocument();
expect(within(editor).getByRole('button', { name: '保存规则' })).toBeInTheDocument();
+ expect(within(editor).queryByRole('region', { name: '告警规则模板' })).not.toBeInTheDocument();
expect(ruleItem).toHaveAttribute('aria-expanded', 'true');
fireEvent.click(within(editor).getByRole('button', { name: '关闭告警规则编辑' }));
@@ -337,6 +351,7 @@ test('keeps mobile rule selection focused and opens editing in a Semi bottom Sid
await waitFor(() => expect(document.querySelector('.v2-alert-rule-editor')).toBeInTheDocument());
const newEditor = screen.getByRole('dialog', { name: '告警规则编辑' });
expect(within(newEditor).getByText('新建规则')).toBeInTheDocument();
+ expect(within(newEditor).getByRole('region', { name: '告警规则模板' })).toBeInTheDocument();
expect(newRule).toHaveAttribute('aria-expanded', 'true');
});
@@ -363,7 +378,9 @@ test('creates auditable drafts from simple offline and hydrogen rule templates',
await waitFor(() => expect(ruleItem).toHaveAttribute('aria-pressed', 'true'));
expect(within(ruleItem).getByText('重要').closest('.semi-tag')).toBeTruthy();
expect(within(ruleItem).getByText('已启用').closest('.semi-tag')).toBeTruthy();
+ expect(screen.queryByRole('region', { name: '告警规则模板' })).not.toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: /新建规则/ }));
const offlineTemplate = await screen.findByRole('button', { name: /离线超过 10 小时/ });
expect(offlineTemplate).toHaveClass('semi-button', 'v2-alert-template-card');
expect(within(offlineTemplate).getByText('GB32960 / JT808 / YUTONG_MQTT').closest('.semi-tag')).toBeTruthy();
diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx
index 9cdfd409..f2f4112a 100644
--- a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx
+++ b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx
@@ -368,7 +368,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
{toggleButton}
}
-
+ {!draft.version ?
快速创建从常用业务场景开始,再按车辆、车型或企业收窄范围。
-
+ : null}
@@ -434,7 +434,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
{rule.enabled ? '已启用' : '已停用'}
)}
- {!rules.length ? : null}
+ {!rules.length ? : null}
{!mobileLayout ? editor : null}
@@ -454,6 +454,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
function NotificationsWorkspace({ editable }: { editable: boolean }) {
const queryClient = useQueryClient();
+ const mobileLayout = useMobileLayout();
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(20);
const notifications = useQuery({
@@ -470,17 +471,22 @@ function NotificationsWorkspace({ editable }: { editable: boolean }) {
useEffect(() => {
if (offset > 0 && offset >= total && !notifications.isPending) setOffset(Math.max(0, (totalPages - 1) * limit));
}, [limit, notifications.isPending, offset, total, totalPages]);
- return !item.read)} onClick={() => read.mutate(items.filter((item) => !item.read).map((item) => item.id))}>{read.isPending ? '正在更新' : '本页全部已读'} : 只读} />{notifications.isError ? notifications.refetch()} /> : null}{read.isError ? : null}
+ const totalTag = {total.toLocaleString('zh-CN')} 条;
+ return 通知列表{totalTag} : '站内通知'} description="仅站内通道具备真实送达与已读状态" meta={mobileLayout ? undefined : totalTag} actions={editable ? : 只读} />{notifications.isError ? notifications.refetch()} /> : null}{read.isError ? : null}
{notifications.isPending ?
: items.length ?
{items.map((item) => {item.title}} headerExtraContent={{item.read ? '已读' : '未读'}} headerLine bodyStyle={{ padding: 0 }}>
{item.content}
)} : !notifications.isError ?
} title="暂无站内通知" description="告警触发或状态变更后,通知会在这里形成可追溯记录。" /> : null}
- 外部通知通道} headerLine bodyStyle={{ padding: 0 }}>预留 · 未启用 },
- { key: '邮件(Email)', value: 预留 · 未启用 },
- { key: '企业微信(WeCom)', value: 预留 · 未启用 }
- ]} />
+
+ 外部通知通道短信、邮件与企业微信均为预留能力3 个 · 未启用}>
+ 预留 · 未启用 },
+ { key: '邮件(Email)', value: 预留 · 未启用 },
+ { key: '企业微信(WeCom)', value: 预留 · 未启用 }
+ ]} />
+
+
;
}
diff --git a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
index bbd6c779..ac829650 100644
--- a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
+++ b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
@@ -7940,17 +7940,56 @@
.v2-alert-notification-list {
min-height: 0;
overflow: auto;
- padding: 12px;
+ padding: 10px;
+ scrollbar-color: #aebdce transparent;
+ scrollbar-width: thin;
+}
+
+.v2-alert-notification-list::-webkit-scrollbar {
+ width: 10px;
+}
+
+.v2-alert-notification-list::-webkit-scrollbar-thumb {
+ border: 2px solid transparent;
+ border-radius: 999px;
+ background: #aebdce;
+ background-clip: padding-box;
}
.v2-alert-notification-cards.semi-card-group {
grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 11px;
+ gap: 8px;
}
.v2-alert-notification-card.semi-card {
content-visibility: auto;
- contain-intrinsic-size: auto 132px;
+ contain-intrinsic-size: auto 116px;
+}
+
+.v2-alert-notification-card > .semi-card-header {
+ min-height: 44px;
+ padding-inline: 11px;
+}
+
+.v2-alert-notification-card > .semi-card-body {
+ min-height: 72px;
+ gap: 0;
+}
+
+.v2-alert-notification-content {
+ min-height: 42px;
+ padding: 8px 11px 5px;
+ line-height: 1.5;
+}
+
+.v2-alert-notification-card > .semi-card-body > footer {
+ min-height: 30px;
+ padding: 3px 7px 3px 11px;
+}
+
+.v2-alert-notification-card footer .semi-button {
+ height: 24px;
+ padding-inline: 7px;
}
.v2-alert-notification-pagination {
@@ -7964,15 +8003,77 @@
padding: 6px 12px;
}
-.v2-alert-channel-card.semi-card {
- border-width: 1px 0 0;
- border-radius: 0;
- box-shadow: none;
+.v2-alert-notification-total.semi-tag {
+ min-height: 24px;
+ border-radius: 999px;
+ padding-inline: 9px;
+ font-variant-numeric: tabular-nums;
}
-.v2-alert-channel-card > .semi-card-header {
- min-height: 38px;
- padding: 0 14px;
+.v2-alert-channel-collapse.semi-collapse {
+ overflow: hidden;
+ border: 0;
+ border-top: 1px solid #e4eaf1;
+ border-radius: 0;
+ background: #fff;
+}
+
+.v2-alert-channel-collapse > .semi-collapse-item {
+ margin: 0;
+ border: 0;
+}
+
+.v2-alert-channel-collapse .semi-collapse-header {
+ min-height: 42px;
+ padding: 5px 14px;
+}
+
+.v2-alert-channel-collapse .semi-collapse-header:hover {
+ background: #f7faff;
+}
+
+.v2-alert-channel-collapse .semi-collapse-content {
+ border-top: 1px solid #edf1f5;
+ background: #fff;
+}
+
+.v2-alert-channel-collapse .semi-collapse-content-wrapper {
+ padding: 0;
+}
+
+.v2-alert-channel-heading {
+ display: flex;
+ width: 100%;
+ min-width: 0;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.v2-alert-channel-heading > span {
+ display: grid;
+ min-width: 0;
+ gap: 1px;
+}
+
+.v2-alert-channel-heading strong {
+ color: #4c5e74;
+ font-size: 11px;
+}
+
+.v2-alert-channel-heading small {
+ overflow: hidden;
+ color: #8995a5;
+ font-size: 9px;
+ font-weight: 400;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.v2-alert-channel-heading > .semi-tag {
+ flex: 0 0 auto;
+ border-radius: 999px;
+ font-size: 9px;
}
.v2-alert-channel-descriptions.semi-descriptions {
@@ -8484,30 +8585,139 @@
justify-content: center;
}
+ .v2-alert-page.is-notifications {
+ height: 100%;
+ min-height: 0;
+ overflow: hidden;
+ padding-bottom: 8px;
+ }
+
.v2-alert-notifications.semi-card {
- min-height: 540px;
- flex: none;
+ min-height: 0;
+ flex: 1 1 0;
}
.v2-alert-notifications > .semi-card-body {
- grid-template-rows: 58px minmax(320px, 1fr) 76px auto;
+ grid-template-rows: 52px minmax(0, 1fr) 58px auto;
+ }
+
+ .v2-alert-notifications > .semi-card-body > .v2-alert-notification-heading {
+ min-height: 52px;
+ padding: 6px 8px 6px 10px;
+ }
+
+ .v2-alert-notification-heading .v2-workspace-panel-copy {
+ flex: 1 1 auto;
+ gap: 0;
+ }
+
+ .v2-alert-notification-heading .v2-workspace-panel-copy > h5.semi-typography {
+ font-size: 14px;
+ }
+
+ .v2-alert-notification-heading .v2-workspace-panel-copy > .semi-typography:not(h5) {
+ display: none;
+ }
+
+ .v2-alert-notification-heading .v2-workspace-panel-actions {
+ flex: 0 0 auto;
+ gap: 5px;
+ }
+
+ .v2-alert-notification-heading .v2-workspace-panel-actions > .semi-button {
+ min-height: 32px;
+ border-radius: 8px;
+ padding-inline: 8px;
+ font-size: 10px;
+ }
+
+ .v2-alert-notification-total.semi-tag {
+ min-height: 22px;
+ padding-inline: 7px;
+ font-size: 9px;
+ }
+
+ .v2-alert-notification-mobile-title {
+ display: flex;
+ min-width: 0;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .v2-alert-notification-mobile-title > .v2-alert-notification-total.semi-tag {
+ flex: 0 0 auto;
+ }
+
+ .v2-alert-notification-list {
+ padding: 6px;
}
.v2-alert-notification-cards.semi-card-group {
grid-template-columns: 1fr;
+ gap: 6px;
+ }
+
+ .v2-alert-notification-card.semi-card {
+ border-radius: 9px;
+ contain-intrinsic-size: auto 102px;
+ }
+
+ .v2-alert-notification-card > .semi-card-header {
+ min-height: 40px;
+ padding-inline: 9px;
+ }
+
+ .v2-alert-notification-title {
+ gap: 5px;
+ }
+
+ .v2-alert-notification-title .v2-alert-severity.semi-tag,
+ .v2-alert-notification-card > .semi-card-header .semi-card-header-wrapper-extra > .semi-tag {
+ min-height: 20px;
+ }
+
+ .v2-alert-notification-title > span:last-child {
+ font-size: 11px;
+ }
+
+ .v2-alert-notification-card > .semi-card-body {
+ min-height: 62px;
+ }
+
+ .v2-alert-notification-content {
+ min-height: 34px;
+ padding: 6px 9px 3px;
+ font-size: 10px;
+ line-height: 1.45;
+ -webkit-line-clamp: 1;
+ }
+
+ .v2-alert-notification-card > .semi-card-body > footer {
+ min-height: 28px;
+ padding: 2px 5px 2px 9px;
+ }
+
+ .v2-alert-notification-card footer .semi-typography,
+ .v2-alert-notification-card footer .semi-button {
+ font-size: 9px;
+ }
+
+ .v2-alert-notification-card footer .semi-button {
+ height: 24px;
+ padding-inline: 6px;
}
.v2-alert-notification-pagination {
- min-height: 76px;
- align-items: stretch;
- justify-content: center;
- flex-direction: column;
- gap: 7px;
- padding: 8px 10px;
+ min-height: 58px;
+ align-items: center;
+ justify-content: flex-end;
+ flex-direction: row;
+ gap: 5px;
+ padding: 5px 6px;
}
.v2-alert-notification-pagination .v2-table-pagination-info {
- width: 100%;
+ display: none;
}
.v2-alert-notification-pagination .v2-table-pagination-controls {
@@ -8518,6 +8728,25 @@
flex: 1 1 auto;
}
+ .v2-alert-notification-pagination .v2-table-pagination-button.semi-button,
+ .v2-alert-notification-pagination .v2-table-pagination-current,
+ .v2-alert-notification-pagination .v2-table-pagination-size.semi-select {
+ height: 34px;
+ }
+
+ .v2-alert-channel-collapse .semi-collapse-header {
+ min-height: 40px;
+ padding: 4px 8px;
+ }
+
+ .v2-alert-channel-heading small {
+ max-width: 190px;
+ }
+
+ .v2-alert-channel-heading > .semi-tag {
+ font-size: 8px;
+ }
+
.v2-alert-channel-descriptions tbody {
display: table-row-group;
}