弹窗的主体设计没什么特别,就是把细分化后的各个功能封装在一个个的小组件内,然后再整合。这样逻辑就分开了,不乱。
弹窗容器
这个容器是弹窗主体的根组件(不含遮罩),要能根据主题的变化能做出相应的改变。还要记录渲染后的主体的大小参数,原为有最大化和最小化的操作,这些操作是依据当前主体的大小而计算得来的。
_ModelContianer.jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import { useRef, useState } from 'react';
import { Paper } from '@mui/material';
import { useModelState } from './_useModel';
import { infoLevel } from './_ModelConfigure';// 计算不同状态下的高度
const calHeight = (sizeMode, normalHeight) => {switch (sizeMode) {case 0:return '45px';case 1:return normalHeight > 0 ? normalHeight + 'px' : 'auto';case 2:return '100vh';default:return 'auto';}
}// 最大化时的固定样式
const maxSizeCss = css`width: 100vw;height: 100vh;top: 0;left: 0;`;/*** 弹窗容器* @param {*} param0 * @returns */
const ModelContainer = ({ children }) => {const modelState = useModelState();const {stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化level, // 弹窗的类型(主要是颜色类型),选项有:default, error, warning, success, infoisDark, //是否是暗黑模式 } = modelState;const [nomalSize, setNormalSize] = useState({ width: 0, height: 0 });const containerRef = useRef(null);return (<Paperref={containerRef}css={css`border: 1px solid #A0A0A0;border-radius: 5px;width: ${ stateMode === 2 ? '100vw' : stateMode === 0 ? '300px' : '576px' };height: ${ calHeight(stateMode, nomalSize.height) };overflow: hidden;max-width: 100%;max-height: 100vh;display: flex;flex-direction: column;background-color: ${isDark ? '#333' : infoLevel[level] ? infoLevel[level].color : "white" };${stateMode === 2 ? maxSizeCss : null};transition: all 0.3s;`}onPointerEnter={() => {if (nomalSize.width === 0) {const rect = containerRef.current.getBoundingClientRect();setNormalSize({width: rect.width,height: rect.height,});} }}>{children}</Paper>);
};export default ModelContainer;
控制按钮
就是最大化、最小化、关闭按钮,最大化按钮和最小化按钮的功能是互斥的,也就是说最大化时不能最小化,最小化时不能最大化。
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import Stack from '@mui/material/Stack';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import Fullscreen from '@mui/icons-material/Fullscreen';
import MinimizeIcon from '@mui/icons-material/Minimize';
import { useModelState } from './_useModel';/*** 弹窗的控制按钮, 最小化,最大化,关闭* @param { 弹窗大小状态, 0: 最小化; 1: 正常; 2: 最大化} sizeMode* @param {设置弹窗大小状态} setSizeMode* @returns */
function ModelController() {const modelstate = useModelState();const {stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化setStateMode, // 设置弹窗的状态onClose, //关闭弹窗} = modelstate;// 最大化事件const maxmizeEvent = (e) => {setStateMode(stateMode === 2 ? 1 : 2);};// 最小化事件const minimizeEvent = (e) => {setStateMode(stateMode === 0 ? 1 : 0);};// 关闭事件const onCloseHandler = (e) => {e.preventDefault();e.stopPropagation();onClose && onClose();}return (<Stack direction="row" spacing={1}><IconButtonaria-label="close"color="inherit"size="small"onClick={minimizeEvent}disabled={stateMode === 2}>{<MinimizeIcon fontSize="inherit" />}</IconButton><IconButtonaria-label="close"color="inherit"size="small"onClick={maxmizeEvent}disabled={stateMode === 0}>{stateMode === 2 ? <FullscreenExitIcon fontSize="inherit" /> : <Fullscreen fontSize="inherit" />}</IconButton><IconButtonaria-label="close"color="inherit"size="small"onClick={onCloseHandler}><CloseIcon fontSize="inherit" /></IconButton></Stack>);
};export default ModelController;
标题栏
标题栏有标题图标、标题、控制按钮组。
_ModelHeader.jsx
import React, { useRef } from 'react';
import Stack from '@mui/material/Stack';
import AlertTitle from '@mui/material/AlertTitle';import ModelController from './_ModelController';
import { useModelState } from './_useModel';import { iconSize, infoLevel } from './_ModelConfigure';// 计算标题长度, 最小化时最多显示8个字符, 正常时最多显示20个字符
const calTitle = (sizeMode, title) => {if (sizeMode === 0) {return title.length > 8 ? title.substring(0, 8) + "..." : title;}return title.length > 20 ? title.substring(0, 20) + " ..." : title;
}/*** 弹窗的头部标题栏* @param {*} props * @returns */
function ModelHeader(props) {const containerRef = useRef();const {level = "default", // 弹窗的类型(主要是颜色类型),选项有:normal, error, warning, success, infotitle = "提示", //标题enableController = true,...others} = props;const { Icon, iColor } = infoLevel[level] || infoLevel["default"]; //弹窗的颜色和图标const modelstate = useModelState();const {stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化} = modelstate;return (<Stackref={containerRef}direction="row"justifyContent="space-between"justifyItems={"center"}spacing={2}sx={{p: 1,borderBottom: stateMode === 0 ? "none" : 1,borderColor: "divider",height: 45,}}{...others}><Stackdirection="row"justifyContent="flex-start"alignItems="center"spacing={1}sx={{ userSelect: "none" }}><Icon sx={{ fontSize: iconSize, color: iColor }} /><AlertTitlesx={{userSelect: "none",textOverflow: "ellipsis",overflow: "hidden",wordBreak: "break-all",whiteSpace: "nowrap",}}>{calTitle(stateMode, title)}</AlertTitle></Stack>{enableController && <ModelController />}</Stack>);
};export default ModelHeader;
内容区
这个没什么好说的,就是弹窗的内容呈现部分
_ModelContent.jsx
import React from 'react';
import "./_ModelContent.css";
import Box from '@mui/material/Box';function ModelContent({ children}) {return (<BoxclassName="noscrollbar"sx={{flex: 1,p: 2,pt: 1,pb: 1,width: "100%",overflowY: "auto",userSelect: "none",}}>{children}</Box>)
}export default ModelContent;
功能按钮
这个功能按钮带有Loding状态,是MUILab中自带的。
_StateButton.jsx
import React, { useState } from 'react';
import LoadingButton from '@mui/lab/LoadingButton';/*** 带Loading状态的按钮* @param {按钮标题} title * @param {按钮点击事件, onClick的类型为: (setLoading, setTitle, setDisable, onClose)=> {}} onClick* @param {模态框的关闭事件} onClose* @param {是否为警告按钮} attention* @returns */
export default function StateButton({ title, onClick, onClose, attention = false }) {const [loading, setLoading] = useState(false);const [disabled, setDisable] = useState(false);const [actionTitle, setTitle] = useState(title);const onClickEvent = () => {onClick(setLoading, setTitle, setDisable, onClose);}return (<LoadingButtonvariant={attention ? "contained" : "outlined" }color={attention ? "error" : "primary"}disabled={disabled}loading={loading}onClick={ onClickEvent }size="small">{actionTitle }</LoadingButton>)
}
功能区
就是根据配置的数据生成多少个功能按钮,如下所示:
_ModelActions.jsx
import React from 'react';
import DialogActions from '@mui/material/DialogActions';
import StateButton from './_StateButton';function ModelActions({ actions, onClose }) {return (<DialogActions sx={{ mr: 1 }}>{actions.map((item, index) => {return (<StateButtonkey={index}title={item.title}onClick={item.onClick}onClose={onClose}attention={item.attention}/>)})}</DialogActions>)
}export default ModelActions;
到这里所有主体的子组件都设计 完成了。当然我的文章并没有结束,重头戏还在后面。再回分解。