polish Semi UI alert subworkspaces

This commit is contained in:
lingniu
2026-07-18 18:56:01 +08:00
parent db1079ae27
commit 84684f2dc1
3 changed files with 282 additions and 30 deletions

View File

@@ -368,7 +368,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
</span>
{toggleButton}
</div>}
<section className="v2-alert-rule-templates" aria-label="告警规则模板">
{!draft.version ? <section className="v2-alert-rule-templates" aria-label="告警规则模板">
<div><strong></strong><span></span></div>
<nav>{RULE_TEMPLATES.map((template) => {
const available = metrics.some((metric) => metric.key === template.draft.metric && metric.alertable);
@@ -385,7 +385,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
<Tag size="small" color={available ? 'blue' : 'grey'} type="light">{available ? (template.draft.scopeProtocols.join(' / ') || '全部协议') : '不可用'}</Tag>
</Button>;
})}</nav>
</section>
</section> : null}
<div className="v2-rule-form-grid">
<label><span></span><Input required maxLength={80} value={draft.name} onChange={(value) => setDraft({ ...draft, name: value })} /></label>
<label><span></span><Select value={draft.severity} onChange={(value) => setDraft({ ...draft, severity: String(value) as AlertRuleInput['severity'] })} optionList={[{ value: 'critical', label: '紧急' }, { value: 'major', label: '重要' }, { value: 'minor', label: '一般' }]} /></label>
@@ -434,7 +434,7 @@ function RulesWorkspace({ rules, metrics }: { rules: AlertRule[]; metrics: Metri
</span>
<Tag className="v2-alert-rule-state" size="small" color={rule.enabled ? 'green' : 'grey'} type="light">{rule.enabled ? '已启用' : '已停用'}</Tag>
</Button>)}
{!rules.length ? <Empty className="v2-alert-rule-empty" title="暂无告警规则" description="从右侧模板快速创建,或新建一条自定义规则。" /> : null}
{!rules.length ? <Empty className="v2-alert-rule-empty" title="暂无告警规则" description="新建规则后可从常用模板开始,也可直接配置自定义条件。" /> : null}
</div>
</Card>
{!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 <Card className="v2-alert-notifications" bodyStyle={{ padding: 0 }}><WorkspacePanelHeader title="站内通知" description="仅站内通道具备真实送达与已读状态" meta={`${total.toLocaleString('zh-CN')}`} actions={editable ? <Button theme="light" disabled={read.isPending || !items.some((item) => !item.read)} onClick={() => read.mutate(items.filter((item) => !item.read).map((item) => item.id))}>{read.isPending ? '正在更新' : '本页全部已读'}</Button> : <span className="v2-role-badge"></span>} />{notifications.isError ? <InlineError message={notifications.error?.message ?? '站内通知读取失败'} onRetry={() => notifications.refetch()} /> : null}{read.isError ? <InlineError message={read.error instanceof Error ? read.error.message : '通知状态更新失败'} /> : null}
const totalTag = <Tag className="v2-alert-notification-total" color="blue" type="light" size="small">{total.toLocaleString('zh-CN')} </Tag>;
return <Card className="v2-alert-notifications" bodyStyle={{ padding: 0 }}><WorkspacePanelHeader className="v2-alert-notification-heading" title={mobileLayout ? <span className="v2-alert-notification-mobile-title">{totalTag}</span> : '站内通知'} description="仅站内通道具备真实送达与已读状态" meta={mobileLayout ? undefined : totalTag} actions={editable ? <Button theme="light" type="primary" disabled={read.isPending || !items.some((item) => !item.read)} onClick={() => read.mutate(items.filter((item) => !item.read).map((item) => item.id))}>{read.isPending ? '正在更新' : mobileLayout ? '本页已读' : '本页全部已读'}</Button> : <span className="v2-role-badge"></span>} />{notifications.isError ? <InlineError message={notifications.error?.message ?? '站内通知读取失败'} onRetry={() => notifications.refetch()} /> : null}{read.isError ? <InlineError message={read.error instanceof Error ? read.error.message : '通知状态更新失败'} /> : null}
<div className="v2-alert-notification-list">{notifications.isPending ? <div className="v2-alert-notification-loading"><Spin size="middle" tip="正在读取站内通知" /></div> : items.length ? <CardGroup className="v2-alert-notification-cards" type="grid" spacing={0}>{items.map((item) => <Card className={`v2-alert-notification-card${item.read ? ' is-read' : ' is-unread'}`} key={item.id} title={<span className="v2-alert-notification-title"><SeverityTag severity={item.severity} /><span title={item.title}>{item.title}</span></span>} headerExtraContent={<Tag color={item.read ? 'grey' : 'orange'} type="light" size="small">{item.read ? '已读' : '未读'}</Tag>} headerLine bodyStyle={{ padding: 0 }}>
<p className="v2-alert-notification-content" title={item.content}>{item.content}</p>
<footer><Typography.Text type="tertiary"><AlertTime value={item.createdAt} /></Typography.Text>{editable && !item.read ? <Button theme="borderless" disabled={read.isPending} onClick={() => read.mutate([item.id])}>{read.isPending ? '更新中' : '标为已读'}</Button> : null}</footer>
</Card>)}</CardGroup> : !notifications.isError ? <Empty className="v2-alert-notification-empty" image={<IconBell />} title="暂无站内通知" description="告警触发或状态变更后,通知会在这里形成可追溯记录。" /> : null}</div>
<footer className="v2-alert-notification-pagination"><TablePagination page={page} totalPages={totalPages} info={`${total.toLocaleString('zh-CN')} 条 · 本页 ${items.length.toLocaleString('zh-CN')}`} disabled={notifications.isFetching} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={limit} pageSizeLabel="每页通知数" onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={[{ value: 20, label: '20 条/页' }, { value: 50, label: '50 条/页' }]} /></footer>
<Card className="v2-alert-channel-card" title={<strong></strong>} headerLine bodyStyle={{ padding: 0 }}><Descriptions className="v2-alert-channel-descriptions" align="left" size="small" data={[
{ key: '短信SMS', value: <Tag color="grey" type="light" size="small"> · </Tag> },
{ key: '邮件Email', value: <Tag color="grey" type="light" size="small"> · </Tag> },
{ key: '企业微信WeCom', value: <Tag color="grey" type="light" size="small"> · </Tag> }
]} /></Card>
<Collapse className="v2-alert-channel-collapse" keepDOM>
<Collapse.Panel itemKey="external-channels" header={<span className="v2-alert-channel-heading"><span><strong></strong><small></small></span><Tag color="grey" type="light" size="small">3 · </Tag></span>}>
<Descriptions className="v2-alert-channel-descriptions" align="left" size="small" data={[
{ key: '短信SMS', value: <Tag color="grey" type="light" size="small"> · </Tag> },
{ key: '邮件Email', value: <Tag color="grey" type="light" size="small"> · </Tag> },
{ key: '企业微信WeCom', value: <Tag color="grey" type="light" size="small"> · </Tag> }
]} />
</Collapse.Panel>
</Collapse>
</Card>;
}