feat(web): unify Semi UI workspace states

This commit is contained in:
lingniu
2026-07-18 00:59:11 +08:00
parent 159c80b0ae
commit 1cd92715a5
13 changed files with 639 additions and 46 deletions

View File

@@ -1,23 +1,88 @@
import { IconAlertTriangle, IconRefresh } from '@douyinfe/semi-icons';
import { Button, Empty, Spin, Typography } from '@douyinfe/semi-ui';
import type { ReactNode } from 'react';
export function PageLoading({ label = '正在加载车辆数据' }: { label?: string }) {
return <section className="v2-page-state" role="status" aria-live="polite">
<header><Spin size="large" /><div><Typography.Text strong>{label}</Typography.Text><Typography.Text type="tertiary" size="small"></Typography.Text></div></header>
<div className="v2-page-skeleton"><i /><i /><i /><i /></div>
type StateScope = 'page' | 'panel' | 'inline';
type StateKind = 'loading' | 'empty' | 'error';
function stateRole(kind: StateKind) {
return kind === 'error' ? 'alert' : 'status';
}
function StateSurface({
kind,
scope,
title,
description,
className = '',
action,
compact = false
}: {
kind: StateKind;
scope: StateScope;
title: string;
description?: string;
className?: string;
action?: ReactNode;
compact?: boolean;
}) {
const classes = ['v2-state-surface', `is-${kind}`, `is-${scope}`, compact ? 'is-compact' : '', className].filter(Boolean).join(' ');
return <section className={classes} role={stateRole(kind)} aria-live={kind === 'error' ? 'assertive' : 'polite'}>
{kind === 'empty'
? <Empty className={`v2-state-empty${className ? ` ${className}` : ''}`} title={title} description={description}>{action}</Empty>
: <div className="v2-state-message">
<span className="v2-state-icon">{kind === 'loading' ? <Spin size={scope === 'page' ? 'large' : 'middle'} /> : <IconAlertTriangle />}</span>
<div className="v2-state-copy"><Typography.Text strong>{title}</Typography.Text>{description ? <Typography.Text type={kind === 'error' ? 'danger' : 'tertiary'} size="small">{description}</Typography.Text> : null}</div>
{action ? <div className="v2-state-action">{action}</div> : null}
</div>}
{kind === 'loading' ? <div className="v2-state-skeleton" aria-hidden="true"><i /><i /><i /></div> : null}
</section>;
}
export function PageLoading({ label = '正在加载车辆数据' }: { label?: string }) {
return <StateSurface className="v2-page-state" kind="loading" scope="page" title={label} description="导航与筛选状态已保留,页面就绪后会自动显示。" />;
}
export function PanelLoading({
title = '正在读取数据',
description = '当前结果返回后会自动更新。',
className = '',
compact = false
}: {
title?: string;
description?: string;
className?: string;
compact?: boolean;
}) {
return <StateSurface className={className} kind="loading" scope="panel" title={title} description={description} compact={compact} />;
}
export function PanelEmpty({
title,
description,
className = '',
action
}: {
title: string;
description?: string;
className?: string;
action?: ReactNode;
}) {
return <StateSurface className={className} kind="empty" scope="panel" title={title} description={description} action={action} />;
}
export function InlineError({ message, onRetry }: { message: string; onRetry?: () => void }) {
return (
<div className="v2-inline-state is-error" role="alert">
<IconAlertTriangle />
<span>{message}</span>
{onRetry ? <Button theme="light" icon={<IconRefresh />} onClick={onRetry}></Button> : null}
</div>
);
return <StateSurface
className="v2-inline-state"
kind="error"
scope="inline"
title="数据暂时无法加载"
description={message}
action={onRetry ? <Button theme="light" type="danger" icon={<IconRefresh />} aria-label="重试" onClick={onRetry}></Button> : null}
/>;
}
export function EmptyState({ title = '暂无符合条件的车辆' }: { title?: string }) {
return <div className="v2-inline-state"><Empty title={title} /></div>;
return <StateSurface className="v2-inline-state" kind="empty" scope="inline" title={title} />;
}