Material-UI 反馈组件 #
概述 #
MUI 提供了丰富的反馈组件,用于向用户提供操作反馈和状态提示。
text
反馈组件体系
│
├── Dialog 对话框
├── Snackbar 消息提示
├── Alert 警告提示
├── Progress 进度指示器
├── Backdrop 背景遮罩
├── Skeleton 骨架屏
└── Popover 弹出框
Dialog 对话框 #
基础对话框 #
jsx
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material';
function AlertDialog() {
const [open, setOpen] = useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<>
<Button variant="outlined" onClick={handleClickOpen}>
打开对话框
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>确认操作</DialogTitle>
<DialogContent>
<DialogContentText>
你确定要执行此操作吗?此操作无法撤销。
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>取消</Button>
<Button onClick={handleClose} autoFocus>
确认
</Button>
</DialogActions>
</Dialog>
</>
);
}
表单对话框 #
jsx
function FormDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outlined" onClick={() => setOpen(true)}>
添加联系人
</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle>添加新联系人</DialogTitle>
<DialogContent>
<DialogContentText>
请输入联系人的姓名和邮箱地址。
</DialogContentText>
<TextField
autoFocus
margin="dense"
label="姓名"
type="text"
fullWidth
variant="standard"
/>
<TextField
margin="dense"
label="邮箱地址"
type="email"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>取消</Button>
<Button onClick={() => setOpen(false)}>添加</Button>
</DialogActions>
</Dialog>
</>
);
}
确认对话框 #
jsx
function ConfirmationDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outlined" color="error" onClick={() => setOpen(true)}>
删除项目
</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle>确认删除</DialogTitle>
<DialogContent>
<DialogContentText>
此操作将永久删除该项目,无法恢复。确定要继续吗?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>取消</Button>
<Button onClick={() => setOpen(false)} color="error" autoFocus>
删除
</Button>
</DialogActions>
</Dialog>
</>
);
}
全屏对话框 #
jsx
import { useTheme, useMediaQuery } from '@mui/material';
function FullScreenDialog() {
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
return (
<Dialog fullScreen={fullScreen} open={open} onClose={() => setOpen(false)}>
<DialogTitle>全屏对话框</DialogTitle>
<DialogContent>
<DialogContentText>内容区域</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>关闭</Button>
</DialogActions>
</Dialog>
);
}
滚动对话框 #
jsx
function ScrollDialog() {
const [open, setOpen] = useState(false);
const descriptionElementRef = useRef(null);
useEffect(() => {
if (open) {
const { current: descriptionElement } = descriptionElementRef;
if (descriptionElement !== null) {
descriptionElement.focus();
}
}
}, [open]);
return (
<Dialog
open={open}
onClose={() => setOpen(false)}
scroll="paper"
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
>
<DialogTitle id="scroll-dialog-title">滚动对话框</DialogTitle>
<DialogContent dividers>
<DialogContentText id="scroll-dialog-description" ref={descriptionElementRef} tabIndex={-1}>
{[...new Array(50)]
.map(
() => `Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.`,
)
.join('\n')}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>取消</Button>
<Button onClick={() => setOpen(false)}>订阅</Button>
</DialogActions>
</Dialog>
);
}
可拖动对话框 #
jsx
import Draggable from 'react-draggable';
function PaperComponent(props) {
return (
<Draggable handle="#draggable-dialog-title" cancel={'[class*="MuiDialogContent-root"]'}>
<Paper {...props} />
</Draggable>
);
}
function DraggableDialog() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onClose={() => setOpen(false)} PaperComponent={PaperComponent}>
<DialogTitle style={{ cursor: 'move' }} id="draggable-dialog-title">
可拖动对话框
</DialogTitle>
<DialogContent>
<DialogContentText>点击标题栏可以拖动对话框</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={() => setOpen(false)}>
取消
</Button>
<Button onClick={() => setOpen(false)}>订阅</Button>
</DialogActions>
</Dialog>
);
}
Snackbar 消息提示 #
基础用法 #
jsx
import { Snackbar, Button } from '@mui/material';
function SimpleSnackbar() {
const [open, setOpen] = useState(false);
const handleClick = () => setOpen(true);
const handleClose = (event, reason) => {
if (reason === 'clickaway') return;
setOpen(false);
};
return (
<>
<Button onClick={handleClick}>显示消息</Button>
<Snackbar
open={open}
autoHideDuration={3000}
onClose={handleClose}
message="操作成功!"
/>
</>
);
}
带动作的消息 #
jsx
function SnackbarWithAction() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>显示消息</Button>
<Snackbar
open={open}
autoHideDuration={5000}
onClose={() => setOpen(false)}
message="文件已删除"
action={
<Button color="secondary" size="small" onClick={() => setOpen(false)}>
撤销
</Button>
}
/>
</>
);
}
位置 #
jsx
<Snackbar
open={open}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
message="顶部居中"
/>
<Snackbar
open={open}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
message="右下角"
/>
配合 Alert 使用 #
jsx
import { Alert } from '@mui/material';
function CustomizedSnackbars() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outlined" onClick={() => setOpen(true)}>
显示成功消息
</Button>
<Snackbar open={open} autoHideDuration={3000} onClose={() => setOpen(false)}>
<Alert onClose={() => setOpen(false)} severity="success" sx={{ width: '100%' }}>
操作成功完成!
</Alert>
</Snackbar>
</>
);
}
Alert 警告提示 #
基础用法 #
jsx
import { Alert, AlertTitle, Stack } from '@mui/material';
<Stack sx={{ width: '100%' }} spacing={2}>
<Alert severity="error">这是一个错误提示</Alert>
<Alert severity="warning">这是一个警告提示</Alert>
<Alert severity="info">这是一个信息提示</Alert>
<Alert severity="success">这是一个成功提示</Alert>
</Stack>
带标题 #
jsx
<Stack sx={{ width: '100%' }} spacing={2}>
<Alert severity="error">
<AlertTitle>错误</AlertTitle>
这是一个错误提示 — <strong>请检查输入!</strong>
</Alert>
<Alert severity="warning">
<AlertTitle>警告</AlertTitle>
这是一个警告提示 — <strong>请注意!</strong>
</Alert>
<Alert severity="info">
<AlertTitle>信息</AlertTitle>
这是一个信息提示 — <strong>仅供参考!</strong>
</Alert>
<Alert severity="success">
<AlertTitle>成功</AlertTitle>
这是一个成功提示 — <strong>操作完成!</strong>
</Alert>
</Stack>
可关闭 #
jsx
<Alert onClose={() => {}}>这是一个可关闭的提示</Alert>
带图标 #
jsx
<Alert icon={<CheckIcon fontSize="inherit" />} severity="success">
自定义图标
</Alert>
<Alert icon={false} severity="success">
无图标
</Alert>
变体 #
jsx
<Stack spacing={2}>
<Alert variant="outlined" severity="error">Outlined 错误</Alert>
<Alert variant="filled" severity="error">Filled 错误</Alert>
<Alert variant="standard" severity="error">Standard 错误</Alert>
</Stack>
带动作 #
jsx
<Alert
severity="info"
action={
<Button color="inherit" size="small">
撤销
</Button>
}
>
这是一条带动作的提示
</Alert>
Progress 进度指示器 #
线性进度条 #
jsx
import { LinearProgress, Box, Typography } from '@mui/material';
<Stack spacing={2} sx={{ width: '100%' }}>
<LinearProgress />
<LinearProgress color="secondary" />
<LinearProgress color="success" />
<LinearProgress variant="determinate" value={50} />
<LinearProgress variant="buffer" value={50} valueBuffer={75} />
<LinearProgress variant="query" />
</Stack>
圆形进度条 #
jsx
import { CircularProgress } from '@mui/material';
<Stack spacing={2} direction="row">
<CircularProgress />
<CircularProgress color="secondary" />
<CircularProgress color="success" />
<CircularProgress variant="determinate" value={75} />
<CircularProgress size={60} />
<CircularProgress thickness={5} />
</Stack>
带标签的进度条 #
jsx
function LinearProgressWithLabel(props) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(props.value)}%`}</Typography>
</Box>
</Box>
);
}
function CircularProgressWithLabel(props) {
return (
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress variant="determinate" {...props} />
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" component="div" color="text.secondary">
{`${Math.round(props.value)}%`}
</Typography>
</Box>
</Box>
);
}
加载状态 #
jsx
function LoadingButton() {
const [loading, setLoading] = useState(false);
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ m: 1, position: 'relative' }}>
<Button variant="contained" disabled={loading}>
提交
</Button>
{loading && (
<CircularProgress
size={24}
sx={{
color: 'primary.main',
position: 'absolute',
top: '50%',
left: '50%',
marginTop: '-12px',
marginLeft: '-12px',
}}
/>
)}
</Box>
</Box>
);
}
Backdrop 背景遮罩 #
基础用法 #
jsx
import { Backdrop, CircularProgress, Button } from '@mui/material';
function SimpleBackdrop() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>显示遮罩</Button>
<Backdrop sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }} open={open} onClick={() => setOpen(false)}>
<CircularProgress color="inherit" />
</Backdrop>
</>
);
}
自定义内容 #
jsx
<Backdrop open={open} onClick={() => setOpen(false)}>
<Box sx={{ textAlign: 'center', color: 'white' }}>
<CircularProgress color="inherit" />
<Typography sx={{ mt: 2 }}>加载中...</Typography>
</Box>
</Backdrop>
Skeleton 骨架屏 #
基础用法 #
jsx
import { Skeleton, Stack } from '@mui/material';
<Stack spacing={1}>
<Skeleton variant="text" />
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={118} />
</Stack>
动画 #
jsx
<Stack spacing={1}>
<Skeleton animation="pulse" />
<Skeleton animation="wave" />
<Skeleton animation={false} />
</Stack>
卡片骨架屏 #
jsx
function SkeletonCard() {
return (
<Card sx={{ maxWidth: 345, m: 2 }}>
<Skeleton variant="rectangular" height={140} />
<CardContent>
<Skeleton animation="wave" height={10} style={{ marginBottom: 6 }} />
<Skeleton animation="wave" height={10} width="80%" />
</CardContent>
</Card>
);
}
列表骨架屏 #
jsx
function SkeletonList() {
return (
<List>
{[1, 2, 3].map((item) => (
<ListItem key={item}>
<ListItemAvatar>
<Skeleton variant="circular" width={40} height={40} />
</ListItemAvatar>
<ListItemText
primary={<Skeleton animation="wave" height={10} width="80%" />}
secondary={<Skeleton animation="wave" height={10} width="40%" />}
/>
</ListItem>
))}
</List>
);
}
Popover 弹出框 #
基础用法 #
jsx
import { Popover, Typography, Button } from '@mui/material';
function BasicPopover() {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const open = Boolean(anchorEl);
return (
<>
<Button onClick={handleClick}>打开弹出框</Button>
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
>
<Typography sx={{ p: 2 }}>弹出框内容</Typography>
</Popover>
</>
);
}
锚点位置 #
jsx
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Typography sx={{ p: 2 }}>内容</Typography>
</Popover>
悬停弹出框 #
jsx
function MouseOverPopover() {
const [anchorEl, setAnchorEl] = useState(null);
const handlePopoverOpen = (event) => setAnchorEl(event.currentTarget);
const handlePopoverClose = () => setAnchorEl(null);
const open = Boolean(anchorEl);
return (
<>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
>
悬停显示弹出框
</Typography>
<Popover
id="mouse-over-popover"
sx={{ pointerEvents: 'none' }}
open={open}
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>弹出框内容</Typography>
</Popover>
</>
);
}
实战示例:完整反馈系统 #
jsx
import { createContext, useContext, useState } from 'react';
import { Snackbar, Alert, Slide } from '@mui/material';
const FeedbackContext = createContext();
function SlideTransition(props) {
return <Slide {...props} direction="up" />;
}
export function FeedbackProvider({ children }) {
const [notification, setNotification] = useState({
open: false,
message: '',
severity: 'info',
});
const showNotification = (message, severity = 'info') => {
setNotification({ open: true, message, severity });
};
const handleClose = () => {
setNotification({ ...notification, open: false });
};
return (
<FeedbackContext.Provider value={{ showNotification }}>
{children}
<Snackbar
open={notification.open}
autoHideDuration={3000}
onClose={handleClose}
TransitionComponent={SlideTransition}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<Alert onClose={handleClose} severity={notification.severity} sx={{ width: '100%' }}>
{notification.message}
</Alert>
</Snackbar>
</FeedbackContext.Provider>
);
}
export function useFeedback() {
return useContext(FeedbackContext);
}
下一步 #
继续学习 导航组件,了解 AppBar、Drawer、Tabs 等导航组件!
最后更新:2026-03-28