polish Semi UI alert subworkspaces
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user