最近工作中需要开发手机端H5页面,没有使用熟悉的 React + Antd-mobile, React + Taro UI,Vue + uView ,而是使用了 React + Material-UI做了一次全新的尝试。而且正好最近React-router/React-router-dom 强行用 hooks模式 迭代了一个大版本@6;
目的: 看腻了antd的中台样式,体验一下Material的【暗黑模式】以及可配置的【调色板】。
React 相关版本信息:
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-router": "^6.0.2",
"react-router-dom": "^6.0.2",
Material-UI 相关版本信息:
"@emotion/react": "^11.6.0",
"@emotion/styled": "^11.6.0",
"@material-ui/core": "^5.0.0-beta.5",
"@material-ui/styles": "^4.11.4",
"@mui/icons-material": "^5.1.1",
"@mui/lab": "^5.0.0-alpha.56",
"@mui/material": "^5.1.1",
使用组件有:Typography Button Radio Select Switch TextField Badge List Table Tooltip Alert Dialog Progress Accordion Card Paper Menu Pagination Grid Modal Date Range Picker...
遇到的问题:
接入 Material-UI
Material-UI官网注意:组件建议单组件引入,示例:
import { Button, Tabs, Modal, Box} from '@material-ui/core/index';
与
import Button from '@material-ui/core/Button';
区别
上面的引入方式,打包后的包体积是下面的包10倍左右...
主题的配置
在Color 颜色 中简单选取了几个颜色搭配为注释:primary的 dark为800色值、main为600色值、light为400色值; secondary的 dark为700色值、main为500色值、light为200色值;看个人审美,可以使用不同色调进行搭配。
themeConfig.js 文件
const theme = {
pink: {
palette: {
primary: {
light: '#ec407a',
main: '#d81b60',
dark: '#ad1457',
contrastText: '#000',
},
secondary: {
light: '#f48fb1',
main: '#e91e63',
dark: '#c2185b',
contrastText: '#000',
},
},
},
purple: {
palette: {
primary: {
light: '#ab47bc',
main: '#8e24aa',
dark: '#6a1b9a',
},
secondary: {
light: '#ce93d8',
main: '#9c27b0',
dark: '#7b1fa2',
},
},
},
deepPurple: {
palette: {
primary: {
light: '#7e57c2',
main: '#5e35b1',
dark: '#4527a0',
contrastText: '#000',
},
secondary: {
light: '#b39ddb',
main: '#673ab7',
dark: '#512da8',
contrastText: '#000',
},
},
},
blue: {
palette: {
primary: {
light: '#42a5f5',
main: '#1e88e5',
dark: '#1565c0',
contrastText: '#000',
},
secondary: {
light: '#90caf9',
main: '#2196f3',
dark: '#1976d2',
contrastText: '#000',
},
},
},
teal: {
palette: {
primary: {
light: '#26a69a',
main: '#00897b',
dark: '#00695c',
contrastText: '#000',
},
secondary: {
light: '#80cbc4',
main: '#009688',
dark: '#00796b',
contrastText: '#000',
},
},
},
green: {
palette: {
primary: {
light: '#66bb6a',
main: '#43a047',
dark: '#2e7d32',
contrastText: '#000',
},
secondary: {
light: '#a5d6a7',
main: '#4caf50',
dark: '#388e3c',
contrastText: '#000',
},
},
},
amber: {
palette: {
primary: {
light: '#ffca28',
main: '#ffb300',
dark: '#ff8f00',
contrastText: '#000',
},
secondary: {
light: '#ffe082',
main: '#ffc107',
dark: '#ffa000',
contrastText: '#000',
},
},
},
blueGrey: {
palette: {
primary: {
light: '#ECEFF1',
main: '#90A4AE',
dark: '#455A64',
contrastText: '#000',
},
secondary: {
light: '#E0E0E0',
main: '#757575',
dark: '#424242',
contrastText: '#000',
},
},
},
cyan: {
palette: {
primary: {
light: '#E0F7FA',
main: '#00BCD4',
dark: '#00838F',
contrastText: '#000',
},
secondary: {
light: '#E1F5FE',
main: '#039BE5',
dark: '#01579B',
contrastText: '#000',
},
},
},
};
export default theme;
全局的基础CSS样式配置
import themePalette from './themePaletteMode';
import { colorToRgba } from './../../utils/colorToRgba';
const applicationTheme = (color, mode, direction) => ({
direction,
palette: {
type: mode,
primary: themePalette(color, mode).palette.primary,
secondary: themePalette(color, mode).palette.secondary,
action: {
hover: mode === 'dark' ? 'rgba(80,80,80, 0.9)' : 'rgba(80,80,80, 0.05)',
hoverOpacity: 0.05,
},
},
typography: {
useNextVariants: true,
fontFamily: ['Open Sans', 'sans-serif'].join(','),
title: {
fontWeight: 600,
},
body2: {
fontWeight: 500,
},
fontWeightMedium: 600,
},
shade: {
light: '0 10px 15px -5px rgba(62, 57, 107, .07)',
},
glow: {
light: `0 2px 20px -5px ${themePalette(color, mode).palette.primary.main}`,
medium: `0 2px 40px -5px ${themePalette(color, mode).palette.primary.main}`,
dark: `0 2px 40px 0px ${themePalette(color, mode).palette.primary.main}`,
},
rounded: {
small: '8px',
medium: '12px',
big: '20px',
},
shadows:
mode === 'dark'
? [
'none',
`0px 1px 3px 0px rgba(100,100,100, 0.6),0px 1px 1px 0px rgba(100,100,100, 0.4),0px 2px 1px -1px rgba(100,100,100, 0.2)`,
'0px 1px 5px 0px rgba(100,100,100, 0.6),0px 2px 2px 0px rgba(100,100,100, 0.4),0px 3px 1px -2px rgba(100,100,100, 0.2)',
'0px 1px 8px 0px rgba(100,100,100, 0.6),0px 3px 4px 0px rgba(100,100,100, 0.4),0px 3px 3px -2px rgba(100,100,100, 0.2)',
'0px 2px 4px -1px rgba(100,100,100, 0.6),0px 4px 5px 0px rgba(100,100,100, 0.4),0px 1px 10px 0px rgba(100,100,100, 0.2)',
'0px 3px 5px -1px rgba(100,100,100, 0.6),0px 5px 8px 0px rgba(100,100,100, 0.4),0px 1px 14px 0px rgba(100,100,100, 0.2)',
'0px 3px 5px -1px rgba(100,100,100, 0.6),0px 6px 10px 0px rgba(100,100,100, 0.4),0px 1px 18px 0px rgba(100,100,100, 0.2)',
'0px 4px 5px -2px rgba(100,100,100, 0.6),0px 7px 10px 1px rgba(100,100,100, 0.4),0px 2px 16px 1px rgba(100,100,100, 0.2)',
'0px 5px 5px -3px rgba(100,100,100, 0.6),0px 8px 10px 1px rgba(100,100,100, 0.4),0px 3px 14px 2px rgba(100,100,100, 0.2)',
'0px 5px 6px -3px rgba(100,100,100, 0.6),0px 9px 12px 1px rgba(100,100,100, 0.4),0px 3px 16px 2px rgba(100,100,100, 0.2)',
'0px 6px 6px -3px rgba(100,100,100, 0.6),0px 10px 14px 1px rgba(100,100,100, 0.4),0px 4px 18px 3px rgba(100,100,100, 0.2)',
'0px 6px 7px -4px rgba(100,100,100, 0.6),0px 11px 15px 1px rgba(100,100,100, 0.4),0px 4px 20px 3px rgba(100,100,100, 0.2)',
'0px 7px 8px -4px rgba(100,100,100, 0.6),0px 12px 17px 2px rgba(100,100,100, 0.4),0px 5px 22px 4px rgba(100,100,100, 0.2)',
'0px 7px 8px -4px rgba(100,100,100, 0.6),0px 13px 19px 2px rgba(100,100,100, 0.4),0px 5px 24px 4px rgba(100,100,100, 0.2)',
'0px 7px 9px -4px rgba(100,100,100, 0.6),0px 14px 21px 2px rgba(100,100,100, 0.4),0px 5px 26px 4px rgba(100,100,100, 0.2)',
'0px 8px 9px -5px rgba(100,100,100, 0.6),0px 15px 22px 2px rgba(100,100,100, 0.4),0px 6px 28px 5px rgba(100,100,100, 0.2)',
'0px 8px 10px -5px rgba(100,100,100, 0.6),0px 16px 24px 2px rgba(100,100,100, 0.4),0px 6px 30px 5px rgba(100,100,100, 0.2)',
'0px 8px 11px -5px rgba(100,100,100, 0.6),0px 17px 26px 2px rgba(100,100,100, 0.4),0px 6px 32px 5px rgba(100,100,100, 0.2)',
'0px 9px 11px -5px rgba(100,100,100, 0.6),0px 18px 28px 2px rgba(100,100,100, 0.4),0px 7px 34px 6px rgba(100,100,100, 0.2)',
'0px 9px 12px -6px rgba(100,100,100, 0.6),0px 19px 29px 2px rgba(100,100,100, 0.4),0px 7px 36px 6px rgba(100,100,100, 0.2)',
'0px 10px 13px -6px rgba(100,100,100, 0.6),0px 20px 31px 3px rgba(100,100,100, 0.4),0px 8px 38px 7px rgba(100,100,100, 0.2)',
'0px 10px 13px -6px rgba(100,100,100, 0.6),0px 21px 33px 3px rgba(100,100,100, 0.4),0px 8px 40px 7px rgba(100,100,100, 0.2)',
'0px 10px 14px -6px rgba(100,100,100, 0.6),0px 22px 35px 3px rgba(100,100,100, 0.4),0px 8px 42px 7px rgba(100,100,100, 0.2)',
'0px 11px 14px -7px rgba(100,100,100, 0.6),0px 23px 36px 3px rgba(100,100,100, 0.4),0px 9px 44px 8px rgba(100,100,100, 0.2)',
'0px 11px 15px -7px rgba(100,100,100, 0.6),0px 24px 38px 3px rgba(100,100,100 0.14),0px 9px 46px 8px rgba(100,100,100, 0.2)',
]
: [
'none',
`0px 1px 3px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 1px 1px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 2px 1px -1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 1px 5px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 2px 2px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 3px 1px -2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 1px 8px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 3px 4px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 3px 3px -2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 2px 4px -1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 4px 5px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 1px 10px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 3px 5px -1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 5px 8px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 1px 14px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 3px 5px -1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 6px 10px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 1px 18px 0px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 4px 5px -2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 7px 10px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 2px 16px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 5px 5px -3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 8px 10px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 3px 14px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 5px 6px -3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 9px 12px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 3px 16px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 6px 6px -3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 10px 14px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 4px 18px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 6px 7px -4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 11px 15px 1px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 4px 20px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 7px 8px -4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 12px 17px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 5px 22px 4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 7px 8px -4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 13px 19px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 5px 24px 4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 7px 9px -4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 14px 21px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 5px 26px 4px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 8px 9px -5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 15px 22px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 6px 28px 5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 8px 10px -5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 16px 24px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 6px 30px 5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 8px 11px -5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 17px 26px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 6px 32px 5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 9px 11px -5px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 18px 28px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 7px 34px 6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 9px 12px -6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 19px 29px 2px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 7px 36px 6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 10px 13px -6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 20px 31px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 8px 38px 7px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 10px 13px -6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 21px 33px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 8px 40px 7px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 10px 14px -6px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 22px 35px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 8px 42px 7px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 11px 14px -7px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 23px 36px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 9px 44px 8px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
`0px 11px 15px -7px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.2,
)},0px 24px 38px 3px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.14,
)},0px 9px 46px 8px ${colorToRgba(
themePalette(color, mode).palette.primary.light,
0.12,
)}`,
],
components: {
MuiButton: {
contained: {
boxShadow: 'none',
},
root: {
borderRadius: 20,
fontWeight: 600,
},
sizeSmall: {
padding: '7px 12px',
},
},
},
});
export default applicationTheme;
注释:shadows 内容较长
完整api配置项请参考官网
配置 theme白天/夜晚模式
import { useTheme } from '@mui/material/styles';
const theme = useTheme();
const { children } = props;
...
<Box
sx={{
minHeight: '100vh',
bgcolor: theme.palette.mode === 'dark' ? '#000' : '#fff',
color: theme.palette.mode === 'dark' ? '#fff' : '#000',
}}>
{children}
</Box>
注释:theme 配置api中包含 background的配置项,可以使用自定义配置
配置调色板
在applicationTheme函数中已经合并了themePalette(color, mode).palette,这里只需要在themePalette 函数中 进行自定义主题色就可以了,
import themeConfig from './themeConfig'
const themePalette = (color, mode) => {
return themeConfig[color];
};
export default themePalette;
注释:themeConfig文件在上面已经粘贴出来了,这里我没有使用mode参数,可以根据项目实际情况 配置 dark/light不同的主题色。例如:pink下再细分 dark - palette / light - palette;
组件的汉化
以Date Range Picke 为示例:
原始 时间区间选择器
汉化后:
官网中提供了一些api 注释:其中有个注意点:1? 和 2? 两个部门找遍了api文档和 api源码都没有找到相关对应api,只能修改对应组件的源代码了,根据组件源代码 定位到
1? 的位置存在于 组件@mui/lab/CalendarPicker/PickersCalendarHeader.js 中
修改如下:
transKey: month.getMonth() + 1, // 这里将 utils.format(month, 'month') 修改为 month.getMonth() + 1
children: month.getMonth() + 1 // 这里将 utils.format(month, 'month') 修改为 month.getMonth() + 1
2? 的位置存在于 组件@mui/lab/CalendarPicker/PickersCalendar.js中
修改如下:
children: ['日','一','二','三','四','五','六'].map((day, i) => /*#__PURE__*/_jsx(PickersCalendarWeekDayLabel, {
// 这里将 这里将 utils.getWeekdays() 修改为 ['日','一','二','三','四','五','六']
使用 patch-package 将 修改后的打包成 patches包。
另外需注意:在设置inputFormat="yyyy/MM/dd" 后,控制台会弹出警告
The mask "__/__/____" you passed is not valid for the format used yyyy/MM/dd. Falling down to uncontrolled not-masked input.
这里 根据源代码查看 mask 相关配置项如下,暂无好的解决方案。
组件的二次封装
其中比较常用的有Input Select Modal等,下面做个简单介绍
<Paper sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: '100%' }}>
<InputBase
sx={{ ml: 1, flex: 1 }}
placeholder="请输入"
inputProps={{ 'aria-label': '请输入' }}
onKeyUp={(e) => {
...
}}
/>
<IconButton
type="search"
onClick={() => {
...
}}
sx={{ p: 1 }}
aria-label="search">
<SearchIcon />
</IconButton>
</Paper>
<TextField
id="search_select"
select
sx={{ width: 130, bgcolor: 'transparent' }}
value={currency}
onChange={(e) => ...}>
{selectArr.map((option) => (
<MenuItem className="search_item" key={option.value} value={option.value}>
<span style={{ fontSize: 12 }}>{option.label}</span>
</MenuItem>
))}
</TextField>
注释:这里比较注意的是样式的调整
#search_select {
min-height: 30px !important;
padding: 0 0 0 10px !important;
line-height: 30px !important;
}
.search_item {
min-height: 30px;
line-height: 30px;
padding: 0 0 0 10px;
}
Modal组件
const style = {
position: 'absolute',
top: '50%',
left: '50%',
borderRadius: 2,
transform: 'translate(-50%, -50%)',
width: '60vw',
bgcolor: 'background.paper',
pt: 2,
pb: 2,
};
<Modal
open={open}
onClose={handleClose}
hideBackdrop
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description">
<Box sx={style}>
<Typography
sx={{ fontSize: 16, mb: 2, pl: 2, pb: 0.5 }}
variant="h6"
gutterBottom
component="div">
{title}
</Typography>
<Box
sx={{
display: 'flex',
alignItem: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
}}>
...
</Box>
</Box>
</Modal>
总结
Material-UI 可玩性还是比较大的,官网也有很多的demo,但是由于版本升级到5.X后,很多api被废弃了,搭配上需要自己去研究了,总体上来说Material风格还是不错的。在后续的过程中再边总结,边更新文档吧。。。