赞
踩
今天我们来用所学的知识来做一个布局菜单的组件, 针对这个组件我之前写过一个教程 React之布局菜单-CSDN博客,那个呢比较基础,这节课算是对那个教程的一个扩展和补充。这个实例讲完,这个系列就算告一段落了。先看效果
这个教程要求对React
知识的了解要求比较全面,如果你是跟着我这个系统文章一路学来的,应该就能跟得上学习进度。本教程内容很多,很详细,会分为几个章节来讲解。
首先要安装MUI
、 React Router
、React Redux
这是必不可少的。我们除了会完成在开头的动图效果示例之外,还有较完整功能添加,所以,会用到一些没有讲过的功能。
暗黑模式
,也就是两种颜色模式。前端是绕不开Css的,但是对于一个完整的项目来说,写Css就很繁琐,我的主张是,能偷懒就偷懒,不能偷懒想办法偷懒。这不,对于布局中的Grid
和 Flex
方面,Bootstrap
就提供了相当完美的功能了,我认为这方面它比MUI强许多,既然如此,何不做个拿来主义者
呢,何苦自己为难自己呢。书回正传,回到我们的项目,在源目录(src) 下新建一个本章的实例目录:SMenu , 并在这个目录下新建目录 SCSS, 我们把网上下载的Bootstrap5.3的Css文件放到这个目录里。另外,我也提供了两个其它的两个css文件,目录结构如下所示:
关于Bootstrap的样式,请大家自行学习,此处不做详解。
以下是 components.css 的内容
.fade-enter { opacity: 0; transform: translateX(-100%); } .fade-enter-active { opacity: 1; transform: translateX(0%); } .fade-exit { opacity: 1; transform: translateX(0%); } .fade-exit-active { opacity: 0; transform: translateX(100%); } .fade-enter-active, .fade-exit-active { transition: opacity 500ms, transform 500ms; } .my-node-enter { opacity: 0; } .my-node-enter-active { opacity: 1; transition: opacity 200ms; } .my-node-exit { opacity: 1; } .my-node-exit-active { opacity: 0; transition: opacity 200ms; } /* *弹窗动画 */ .speedx-alert-enter { opacity: 0; transform: scale(0.9); } .speedx-alert-enter-active { opacity: 1; transform: translateX(0); transition: opacity 300ms, transform 300ms; } .speedx-alert-exit { opacity: 1; } .speedx-alert-exit-active { opacity: 0; transform: scale(0.9); transition: opacity 300ms, transform 300ms; }
下面是public.css
的内容
html { background-color:#f2f2f2; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 18px; line-height: 1.667; color: #222; text-align: justify; word-wrap: break-word; word-break: break-word; -moz-hyphens: auto; hyphens: auto; } input, textarea { font-family: 'Roboto', sans-serif; line-height: 1.4; background: #eee; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; color: #353535d9; overflow-wrap: break-word; } :not(pre) > code { background-color: rgb(214, 214, 214); border-radius: 3px; padding: 1px 3px; } img { max-width: 100%; max-height: 20em; } .page-container { position: relative; display: flex; flex-direction: column; background-color:white; min-height: 100vh; } .layout-content{ margin: 10px, 0px; padding: 0px; min-height: 100%; flex: 1; text-align:justify; } .content-wrap { padding-bottom: 2.5rem; /* Footer height */ } .footer { position: absolute; bottom: 0; width: 100%; height: 2.5rem; /* Footer height */ padding: 20px 0; /* box-shadow: 3px 0 5px #c9c9c9; */ } blockquote { border-left: 2px solid rgb(1, 154, 192); margin-left: 0; margin-right: 0; padding-left: 10px; color: rgb(150, 150, 150); font-style: italic; } blockquote[dir='rtl'] { border-left: none; padding-left: 0; padding-right: 10px; border-right: 2px solid #ddd; } input { box-sizing: border-box; font-size: 0.85em; width: 100%; padding: 0.5em; border: 2px solid #ddd; background: #fafafa; } input:focus { outline: 0; border-color: blue; } iframe { width: 100%; border: 1px solid #eee; } [data-slate-editor] > * + * { margin-top: 1em; } #root{ display: flex; min-height: 100vh; flex-direction: column; background-color:#f2f2f2; } .alignCenterVH{ position: relative; top: 50%; transform: translateY(-50%); text-align: center; } .mainBoxPosition{ flex: 1; display: flex; justify-content: center; align-items: top; } .titleInput { display: block; width: 100%; font-weight: bold; min-height: 50px; font-size: 22px; border:none; border-bottom: 1px; border-color: rgb(190, 190, 190); outline:none; } .selectElement { display: block; max-width: 100%; max-height: 20em; } .imgsubstring { display: block; color:rgb(116, 116, 116); font-weight: 500; font-size: medium; padding: 5px; text-align: center; } .mayi-select { width: 400px; height: 200px;line-height: 200px;text-align: center;margin:auto; border: 1px solid #ccc; background: linear-gradient(#efefef,#ccc) padding-box, linear-gradient(135deg, rgba(0, 0, 0, 1) 25%, transparent 25%, transparent 50%, rgba(1, 1, 1, 1) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-size:100% 100%, 8px 8px; animation: bg 1s linear infinite; } .mayi-select:hover{ cursor: pointer; border: 1px dashed transparent; } @keyframes bg { 0% { background-position: 0 0; } 100% { background-position: 8px 0; } } .alignCenter { display: table-cell; /*垂直居中 */ vertical-align: middle; /*水平居中*/ text-align: center; /* text-align: center; background-color: #fff; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); */ } .site-layout-background{ background-color: white; } .check-background { width: 100px; height: 100px; background-image: url('data:image/svg+xml,\ <svg xmlns="http://www.w3.org/2000/svg"\ width="100" height="100" fill-opacity=".25">\ <rect x="50" width="50" height="50"></rect>\ <rect y="50" width="50" height="50"></rect>\ </svg>'); background-size:50px 50px; }
设计一个样式组件,在App中引入一下就可以了,就可以保证我们的所有组件就能够应用到我们的样式。在STheme文件夹中创建 AdapterCss.jsx
import CssBaseline from '@mui/material/CssBaseline';
import '../SCSS/public.css';
import '../SCSS/components.css';
import '../SCSS/bootstrap5.3.0/bootstrap-utilities.min.css';
import '../SCSS/bootstrap5.3.0/bootstrap-grid.min.css';
// import '../SCSS/bootstrap5.3.0/bootstrap.min.css';
export default function AdapterCss() {
return <CssBaseline />
}
我们只要在根组件中引入一次这个样式适配器就好了。
因为我们要为App
适配暗模式,所以在设计之初就要考虑好这个问题。首先,MUI所有的组件就已经适配了两种颜色模式,Bootstrap
也是一样。还有一个就是我们自己封装的组件也要适配到暗模式中,就这要求我们自己设计的组件元素应用的颜色模式要么来处MUI, 要么采用Bootstrap
,要么我们自己提供一个双模式的颜色体系。也就是说这三种不同框架之间的颜色体系是共存的。下面我们分别来说一说:
MUI
中提供了两个工具,让我们能构获取和设置颜色模式。
ThemeProvider
这个很好理解,就是一个颜色模式厂,就是一个Context
;createTheme
创建一个颜色模式。我们这里只是用它来改变MUI
的颜色模式;下面我们用示例说明用法:
import { createTheme } from '@mui/material/styles';
function createMuiTheme(mode) {
const themeMode = mode === "light" ? "light" : "dark";
return createTheme({
palette: {
mode: themeMode,
},
});
}
上面的函数根据我们传入的模式关键字来创建相应的MUI
颜色模式。
这就简单了,我们只要改变顶层包裹组件的data-bs-theme
属性值就可以切换颜色模式。
<div data-bs-theme="light"> 这是 light 模式 <div>
<div data-bs-theme="dark"> 这是 dark 模式 <div>
很简单吧。
自定义颜色模式就有点技术含量了。也是最繁琐的一环。首先我们要定义的每种颜色要有两个模式下的颜色值。这就要一个标准,由于我没有采用 TS 设计模式,所以就要用其它的办法来约束定义的行为,比如一个函数就是一个很好的办法。
我们在STheme目录中创建一个工具函数库,把所有的我们自定义的工具函数放到其中统一导出就好了。
// SThemeUtils.jsx import SThemeCodors from "./SColors"; // 生成基本颜色,lightColor为浅色,darkColor为深色 export function sColor(lightColor, darkColor) { return { light: lightColor, dark: darkColor, } } /** * 生成主题模型 * @param {} mode * @returns */ export default function createSTheme(mode = "light") { const themeMode = mode === "light" ? "light" : "dark"; const sTheme = {mode: themeMode}; Object.keys(SThemeCodors).forEach(key => { sTheme[key] = SThemeCodors[key][themeMode]; } ); return sTheme; } /** * 生成MUI系统主题 * @param {*} mode * @returns */ export function createMuiTheme(mode) { const themeMode = mode === "light" ? "light" : "dark"; return createTheme({ palette: { mode: themeMode, }, }); }
sColor
函数生成一个颜色对象,这样行为就统一了。每个颜色对象中都有一个 light
色 和一个 dark
色。所以我们设计之初就要把每种不同模式下的颜色配置好。这关系到我们整体的App
风格。你看,我们设计一个App其实没那么简单对不对,对不同技术技能都要些要求的。createSTheme根据自定义颜色模式生成基于自定义颜色的
theme`现在就是定义颜色了,在相同的目录下,创建颜色库文件
// sColors.jsx import { sColor } from "./SThemeUtils"; /** * 定义主题颜色模型 */ const SThemeColors = { bgColor: sColor("#edf3f2", "#1D1D1D"), //背景色 /** * 菜单色配置 */ badge: sColor("red", "red"), //小红点色 menuBgcolor: sColor("#EEEEEE", "#0D2745"),//菜单栏的背景色 hoverMenuBgcolor: sColor("#FFEACC", "#091C32"), //菜单栏背景色Hover iconColorNormal: sColor("#1c2322", "#EEEEEE"), //图标色 iconColorSquare: sColor("#363c3b", "#CCCCCC"), //无图标时的替代色 menuNomalColor: sColor("#333333", "#07172A"), //菜单栏正常字体色 activeMenuBgcolor: sColor("#FFEACC", "#1C54AD"), //活动菜单背景色 activeBorderColor: sColor("#007AFF", "#1C54AD"), //活动菜单边框色 menuSpliderColor: sColor("#DDDDDD", "#143C6A"), // 菜单栏分隔色 menuSubitemColor: sColor("#545a59", "#B8B8B8"), //子菜单字体色 hoverSubitemColor: sColor("#9fa2a1", "#3C628B"), //hover时的子菜单字体色 hoverMenuSubitemBgcolor: sColor("#FFEACC", "#123862"), //子菜单的hover背景色 activeMenuSubitemBgcolor: sColor("#FFBF66", "#0E2C4D"),//活动子菜单的背景色 activeQuickMenuBgcolor: sColor("#FFBF66", "#2266B5"),//活动快捷菜单的子菜单 } export default SThemeColors;
这就是我们的颜色系统,根据需要自行定义。
现在我们向App提供三种 provider
, 还要提供 切换 模式的方法,最好的办法当然就是 Context
了,我们来设计这几个Provider
: 创建 SThemeContext.jsx
文件:
// SThemeContext.jsx import { createContext } from 'react'; /** * 创建自定义主题上下文 */ export const SThemeContext = createContext(null); export function CusThemeProvider({ theme, children }) { return ( <SThemeContext.Provider value={theme}> { children } </SThemeContext.Provider> ) } /** * 创建切换主题上下文 */ export const ToggleSThemeContext = createContext(null); export function ToggleSThemeProvider({ handler, children }) { return ( <ToggleSThemeContext.Provider value={handler}> { children } </ToggleSThemeContext.Provider> ) } /** * 创建Bootstrap主题上下文 * @param {*} param0 * @returns */ export function BootstrapThemeProvider({ mode, children }) { return ( <div data-bs-theme={mode}> { children } </div> ) }
文件里已经备注的很清楚了,就是创建两个上下文就OK了。
现在三种颜色的框架都有了。接下来我们就是要把这三个模式合并成一个Provider
就完美了。我们来创建这个文件。在STheme
目录下创建 SThemeProvider.jsx
文件
// SThemeProvider.jsx import { useState } from 'react'; import { ThemeProvider } from '@mui/material/styles'; import AdapterCss from './AdapterCss'; import createSTheme, { createMuiTheme } from './SThemeUtils'; import { BootstrapThemeProvider, CusThemeProvider, ToggleSThemeProvider} from './SThemeContext'; /** * 项目的皮肤供应器 * @param {} param0 * @returns */ function SThemeProvider({ children }) { const [theme, changeTheme] = useState({ custom: createSTheme("light"), muiTheme: createMuiTheme("light")}); const toggleThemHandler = () => { const muiThemeMode = theme.muiTheme.palette.mode === "light" ? "dark" : "light"; changeTheme({ custom: createSTheme(muiThemeMode), muiTheme: createMuiTheme(muiThemeMode), }) } return ( <ThemeProvider theme={theme.muiTheme}> <CusThemeProvider theme={theme.custom}> <BootstrapThemeProvider mode={theme.custom.mode}> <ToggleSThemeProvider handler={toggleThemHandler}> <AdapterCss /> { children } </ToggleSThemeProvider> </BootstrapThemeProvider> </CusThemeProvider> </ThemeProvider> ) } export default SThemeProvider;
现在层次很清晰了吧。是不是清爽了许我,这样,我们在根组件中用 SThemeProvider
包裹就好了。是不是很优雅。我们只需要在项目入口文件 main.jsx 中这样写就行了。
import React from 'react'
import ReactDOM from 'react-dom/client'
import SThemeProvider from './SMenu/STheme/SThemeProvider.jsx';
import App from './SMenu/App.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<SThemeProvider>
<App />
</SThemeProvider>
</React.StrictMode>,
)
这是主题最后一个环节,我们要提供一个 Hook 供我们的组件使用,要不然,设计主题有什么意义呢。
在 STheme目录中创建 文件 useToggleThemeHook.jsx
import { useContext } from 'react';
import { ToggleSThemeContext } from './SThemeContext';
// 获取切换主题的功能函数。
const useToggleTheme = () => {
return useContext(ToggleSThemeContext)
}
export default useToggleTheme;
是不是太完美了。 是相当的完美啊。(未完待续)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。