Warden layout

1. 项目说明

基于ant-design打造的适用于中后台系统的开源布局组件,支持多种样式布局、主题、自定义皮肤、权限控制、国际化、消息总线等细节控制,项目中每一套布局都由实际项目落地经验沉淀而来,菜单透明、边线、折叠、预留自定义皮肤扩展插等进行了丰富的细节把控。项目完全开源并已提交至npm仓库。
使用本框架前假设您具备一定的ReactTypeScriptWebpack构建开发经验,并且对antd组件库umi框架有一定的了解。
因为我们了解到采用and-design组件库的方案大多都直接采用umi构建,所以项目中我也成集成了umi,同时保持了路由、签权、结构等与它的一致性,与项目相关的配置和特性请参考umi,下面只介绍Warden-layout布局组件的使用。

2. 技术特性

  • 多个登录页布局样式
  • 多个主架页主题布局交叉切换
  • 布局预留玻璃特效
  • 结合分隔菜和Logo大小属性切换可演变更多的主题样式
  • 支持全局动态国际化
  • 已封装echarts图表组件化(React)
  • 集成后端SSE消息推送
  • 菜单支持多种权限集成方式
  • 菜单图标支持选中模式
  • 预留自定义皮肤方案

3. 开发手册

3.1. 安装方式
可在github免费获取,然后自己在此基础上直接修改布署,或者直接通过npm安装本地项目中进行开发。
$ yarn add warden-layout
/** - or - */
$ npm add warden-layout
npm安装warden-layout需要依赖antd和umi,请先安装好它们。
// package.json
                                
"dependencies": {
    "antd": "^5.21.0",
    "umi": "^4.3.36",
    ...
}
配置好umi相关插件,并关闭mfsu
// config.tsx

import { defineConfig } from "umi"

export default defineConfig({
  plugins: [
    '@umijs/plugins/dist/initial-state',
    '@umijs/plugins/dist/model',
    '@umijs/plugins/dist/access',
    '@umijs/plugins/dist/locale'
  ],
  initialState: {},
  model: {},
  access: {},
  mfsu: false,
})
安装成功后,在src/layouts/index.tsx中引用warden-layout,即可按默认配置完成布局组件的替换,如果项目中有多种布局结构,可以以同样方式在layouts中建多个布局页文件引入warden-layout
// src/layouts/index.tsx
import WardenLayout from 'WardenLayout'
export default() => { return ( <WardenLayout /> ) }
如果修改的不是默认布局或有多个路由布局,需要在路由配置中指定布局。
// src/layouts/company.tsx
import WardenLayout from 'WardenLayout'
export default() => { return ( <WardenLayout /> ) }
// src/routes.tsx
export default [ { path:"/", redirect:"/main", name:"Home", }, { path:"/docs", component: "docs", name:"Docs" }, { path:"/main", component:"@/layouts/company", name:"Main", routes:[...] } ]
3.2.布局配置
warden-layout布局组件提供丰富的配置属性,可以对它进行初始化配置。
// src/layouts/company.tsx
import WardenLayout, { Warden } from 'WardenLayout'
export default() => { const layoutConfig:Warden.IConfig = { "theme": "dark", // 黑暗模式 "systemTheme": true, // 跟随系统主题模式 "layoutType": "headMenu", // 布局类型 "primaryColor": "#417ffb", // 主题颜色 "compact": false, // 紧凑模式 "leftMenuInline": true, "backgroundBlur": true, "leftEmptyHidden": true, "menuSplit": true, "brandLogo": "svg@/images/logo.svg", "brandTitle": "app.main.brand.title", "rootItemMenuGroup": false, ... } return ( <WardenLayout config={layoutConfig} /> ) }
详细配置属性见下表:
  • themeTheme - 明暗模式
  • systemThemeboolean - theme是否跟随系统
  • layoutTypeLayoutType - 布局类型
  • primaryColorstring - 主题颜色
  • menuSkinstring - 主题皮肤
  • menuTransparentboolean - 菜单背景透明
  • backgroundBlurboolean - 菜单背景模糊
  • containerTransparentboolean - 容器组件透明
  • menuBackgroundStyleMenuBackgroundStyle - 菜单背景独立样式
  • menuIconSizenumber - 菜单图标大小
  • compactboolean - 紧凑模式
  • hideBorderboolean - 隐藏线条
  • menuSplitboolean - 分隔菜单
  • leftEmptyHiddenboolean - 左侧菜单为空隐藏
  • subItemMenuTransparentboolean - 子菜单背景透明
  • leftMenuInlineboolean - 左侧菜单内联模式
  • rootItemMenuGroupboolean - 左侧菜单一级分组
  • hideFooterboolean - 全局隐藏页脚
  • hideBreadcrumbboolean - 全局隐藏面包屑
  • hideTitleBarboolean - 全局隐藏标题栏
  • brandLogostring - 品牌Logo
  • brandTitlestring - 品牌标题
  • localeEnabledboolean - 启用国际化(布局)
  • logoNavigateRoutestring - 点击logo导航路由
  • avatarNavigateRoutestring - 形象导航路由
  • avatarReplaceBrandboolean - 形象替换品牌Logo
  • menuIconVariantboolean | string[] - 菜单图标启用激活状态
如果需要动态修改配置,可以使用useConfigContext钩子进行设置。
// src/pages/welcome.tsx
import { useConfigContext } from 'WardenLayout' import { Button } from 'antd'
export default() => { const {config,setConfig} = useConfigContext() return ( <div><Button onClick={()=>{setConfig({...config, primaryColor:"#ff6600"})}}>Change color</Button></div> ) }
注意钩子范围只对当前布局起作用,每个布局的配置都是隔离的。
3.3.布局属性
布局配置只是属性的一种,warden-layout组件另外还有多个插槽式属性组件绑定,例如:自定义皮肤、顶部工具栏按钮、左则底部面版、用户弹出面版等等众多自定义功能。
// Full screen switch icon, it receives an array, and the exit icon is placed in front of it
const layoutConfig:Warden.IConfig = { ... } const screenIcons:JSX.Element[] = [ <SvgIcon src="/svg/menu/main-screen-in.svg" style={{width:"16px",height:"16px"}} />, <SvgIcon src="/svg/menu/main-screen-out.svg" style={{width:"16px",height:"16px"}} /> ] return ( <WardenLayout config={layoutConfig} logoPopover={<CustomLogoPopoverPanel />} toolbarUserPanel={<CustomToolbarUserPanel />} toolbarButtons={[ <GithubButton key="githubButton" />, <CustomBellButton key="bellButton" />, <CustomThemeButton key="themeButton" />]} screenIcons={screenIcons} footer={<FooterPanel />} /> )
所有属性列表:
  • footerJSX.Element- 全局容器页脚
  • toolbarButtonsJSX.Element[]- 工具栏按钮
  • avatarPopoverJSX.Element- 用户形象弹出面版
  • logoPopoverJSX.Element- Logo悬停弹出面版
  • leftExpandPanelJSX.Element - 左侧栏底部扩展面版
  • screenIconsJSX.Element[] - 全屏切换图标
  • wardenMenuDataIMenuData[] - 菜单数据
3.4. 菜单和路由
Warden-layout遵循umi的菜单路由原则,菜单和路由进行了深度绑定,并在此基础上进行了扩充,例如:增加了权限的简化配置、菜单气泡的初始化以及消息钩子等功能。umi菜单配置通常单独放在routes.tsx文件中,然后引入到config.tsx中。例如下面这样:
// config.tsx
import { defineConfig }from 'umi' import routes from './routes'
export default defineConfig({ initialState: {}, model: {}, ..., routes: routes })
这里假设你对umi有一定的了解,下面只说明warden-laout中菜单的特性:
➰菜单图标(icon):默认使用集成的@ant-design/icons图标库,只需要给图标名称即可,例如CaretUpOutlined,同时支持url图标(svg和png),还支持umi图标库,使用方法:图标类型前缀加‘@’再加图标名或url即可。
// routes.tsx
export default [ { path:"/", icon:"CaretUpOutlined" ... }, { path:"/docs", icon: "umi@local:doc" ... }, { path:"/docs", icon: "img@/images/cb.png" ... }, { path:"/main", icon: "svg@https://xxx.com/xxx.svg" ... }, ]
可设置布局配置项menuIconSize调整图标大小,menuIconVariant激活选中状态。
menuIconVariant如果设为true表示启用默认给图标名后缀加上“Filled”和“Filled”字段进行切换选中状态,要确保图库和路径中有这些图标存在,否则显示异常,如果是自定义的后缀,可以配置一个2个长度的数组:['-unchecked','checked']
// src/layout/company.tsx
import WardenLayout, { Warden } from 'WardenLayout'
export default() => { const layoutConfig:Warden.IConfig = { "menuIconVariant": ['-unchecked','-checked'], ... } return ( <WardenLayout config={layoutConfig} /> ) }
➰菜单扩展项:可以给菜单项增加初始化的汽冒(badge)和标签(Tag)内容,而且后面还可以用消息事件进行动态更新。
// routes.tsx
export default [ { path:"/main/discover/welcome", badge:"12" ... }, { path:"/main/discover/introduce", badge:{ position:"left" } ... }, { path:"/main/discover/message", tag:"New" ... }, ]
菜单扩展项的动态更新需要用到消息总线,warden-layout已封装好了监听和抽象,注意这里没有用到钩子,而且是全局的消息事件,需要先用IExtraBadgePropsIExtraTagProps构建消息体,然后利用原生CustomEvent发送消息。
// src/pages/demo.tsx
import { IExtraBadgeProps,IExtraTagProps,IMenuMessageEvent } from 'WardenLayout' import { Button } from 'antd'
const sendMenuMessage=(e:IMenuMessageEvent)=>{ const sender = new CustomEvent("menu-message",{detail:e}) window.dispatchEvent(sender) } export default() => { // 向指定菜单发送随机汽泡信息 const randomMessage = () => { const newCount = Math.floor(Math.random() * 100); const badgeData:IExtraBadgeProps = { filterKey:"path", filterValue:"/main/discover/message", data:{ count:newCount, position:"left", dot:false, color:"red", } } sendMenuMessage({id:"badge",data:badgeData}) } // 向指定菜单发送标签信息 const setMenuTag = (data:any) => { const tagData:IExtraTagProps = { filterKey:"path", filterValue:"/main/discover/introduce", data } sendMenuMessage({id:"tag",data:tagData}) } return ( <div> <Button onClick={()=>{ randomMessage () }}>Send random badge</Button> <Button onClick={()=>{ setMenuTag ({color:"green",text:"Success"}) }}>Send tag data</Button> </div> ) }
➰菜单权限:菜单鉴权有两种方式,1是使用umi自带的access插件,访方式需要配置access.tsx,然后在菜单中配置需要的access即可,具体可以了解umi权限处理,2是使用warden-layout的简化处理方式,不需要配置额外处理权限逻辑,只需要在菜单中的字段authorities直接配置后端拿到的权限值即可。
// routes.tsx

export default [
    { path:"/main/discover/welcome", access:"canRead" ... },  // umi的access鉴权方式
    { path:"/main/discover/introduce", authorities:[ "user:read","user:write" ] ... }  // warden-layout 简洁方式
]

/** src/access.js
 *  umi的鉴权方式需要额外配置这里
 */
export default function (initialState) {
    const { currentUser } = initialState;
    return {
        canRead:()=>{
            return ["user:read","user:auery"].some(item=>currentUser.authorities.includes(item))}
    }
}
3.5. 环境变量
在warden-layout使用环境变量.env配置,需要先导入到umi配置中。
warden-layout自身有两个环境变量比较重要,ENABLE_SETTINGENABLE_CONFIG_STORAGE,ENABLE_SETTING:用于开启抽屉配置组件,默认是关闭的,这个功能一般只是在开发环境下使用,但如果想在其它环境下使用配置开启即可(不推荐)。ENABLE_CONFIG_STORAGE:是开启配置实时存储功能,默认是开启的,如果你想固定配好的布局效果,请关闭它。
// config.tsx

import { defineConfig } from "umi"

export default defineConfig({
    ...
    define: { 
        "process.env.ENABLE_SETTING": process.env.ENABLE_SETTING,
        "process.env.ENABLE_CONFIG_STORAGE": process.env.ENABLE_CONFIG_STORAGE,
        "process.env.API_URI": process.env.API_URI
    } 
})

// .env

ENABLE_SETTING = false
ENABLE_CONFIG_STORAGE = true
API_URI = https://api.warden.vip
3.6. 自定义皮肤
warden-layout的亮点特性,自定义皮肤功能作为插槽属性开放,可以用css、svg、图片等设计出许多丰富精美的皮肤,示列:warden admin pro,自定义皮肤需要设计出一张大的背景图,或SVG和Canvas元素,然后实现IMenuSkin相关参数,可以设计多个皮肤,然后在umi运行时配置中挂载,接着在当前布局配置好所要使用哪一个主题,一个布局只能使用一个主题,但可以有多个挂载轮流使用。
① 设计和配置好皮肤
// src/commponents/AppSkin.tsx

import { Warden } from 'WardenLayout'

const defaultSkins:Warden.IMenuSkin[] = [
    {
        theme:"light",
        primaryColor:"#358cf1",
        name:"blueSky",
        label:"blueSky",
        backgroundImage:"/images/skins/blue-sky-bg.png",
        icon:"/images/skins/blue-sky-small.png"
    },{
        theme:"light",
        primaryColor:"#ff677b",
        name:"pinkRomantic",
        label:"pinkRomantic",
        backgroundImage:"/images/skins/pink-romantic-bg.jpg",
        icon:"/images/skins/pink-romantic-small.jpg"
    }
    ...
]
                                
② 运行时配置挂载皮肤
// app.tsx

import { Warden } from 'WardenLayout'
import { defaultSkins } from './components/AppSkin'

export async function getInitialState():Promise<{
    skins?:Warden.IMenuSkin[];
    currentUser?:Warden.IUser;
    getUserInfo?:() => Promise<Warden.IUser | undefined>;
}> {
    const getUserInfo = async() => {
        // 获取用户信息
        const userInfo = {
            ...
        }
        return userInfo
    }
    return {
        getUserInfo,
        skins:defaultSkins,  // 自定义皮肤挂载
        currentUser:await getUserInfo()
    }
}
③ 使用主体皮肤:直接在布局配置皮肤名称即可,还可以用钩子动态切换皮肤。
// src/layout/company.tsx
import WardenLayout, { Warden } from 'WardenLayout'
export default() => { const layoutConfig:Warden.IConfig = { "menuSkin": "blueSky" ... } return ( <WardenLayout config={layoutConfig} /> ) }
想动态修改皮肤可以使用上面提到的useConfigContext钩子进行设置。
// src/pages/welcome.tsx
import { useConfigContext } from 'WardenLayout' import { Button } from 'antd'
export default() => { const {config,setConfig} = useConfigContext() return ( <div><Button onClick={()=>{setConfig({...config, menuSkin:"blueSky"})}}>Change color</Button></div> ) }
IMenuSkin 所有字段如下:
  • namestring- 皮肤唯一名称
  • primaryColorstring- 主题颜色
  • labelstring- 皮肤显示名称(自动按skin.{name}.label进行国际化)
  • themeTheme- 明暗主题
  • layoutTypeLayoutType - 布局类型
  • menuBackgroundStyleMenuBackgroundStyle - 菜单背景样式
  • backgroundImagestring - 主体背景图片(与内容互斥)
  • iconstring - 皮肤缩略图
  • contentJSX.Element - 主体背景内容(与图片互斥)
3.7.容器组件
容器Container组件是warden-layout自带的用于统一右侧的包装器,可以更方便的统一设置内容的拉伸、边距、透明效果。它并不是必须的,但推荐使用。使用方法介绍:
// src/pages/welcome.tsx
import { Container } from 'WardenLayout' import { Button } from 'antd'
export default() => { return ( <Container mode="panel"> <div> <Button>Change color</Button> </div> </Container> ) }
容器所有属性如下:
  • modeContainerMode- 容器模式
  • hideTitleboolean- 隐藏标题
  • hideBreadcrumbstring- 隐藏面包屑导航
  • hideFooterTheme- 隐藏页脚
  • transparentLayoutType - 容器透明
  • borderedboolean - 是否带边框
  • menuByBackgroundboolean - 容器背景随菜单特效
  • stretchContainerStretch - 容器高度伸缩模式
3.8. 所有类型参数
: 明暗主题类型
lightstring - 明亮模式
darkstring - 黑暗模式
: 布局类型
headMenustring - 主菜单在顶部
leftMenustring - 主菜单在左侧
: 主菜单背景样式
normalstring - 默认随主题和皮肤
blackstring - 接近于黑色
primarystring - 采用主题色
: 容器模式
nonestring - 无内外边距
boxstring - 有20px外边距,背景透明
panelstring - 内外都有20边距,有背景色
: 容器高度拉伸模式(只在容器为panel模式下有效)
nonestring - 内容高度
autostring - 外边距会铺满但是透明的
fullstring - 带背景完全铺满
: 菜单图标类型
antstring - ant-design/icons图标库
umistring - umi图库组件
svgstring - svg图片
imgstring - png位图

4. 版本更新

➕ 增加皮肤加载效果
🔨 优化菜单popup状态下badge显示问题
🔨 修正全屏模式下切换路由产生的线节问题
💔 移除warden菜单图标
🔨 修正菜单国际化的问题
🔨 修正左侧品牌logo与用户头像切换的问题
🍹 优化菜单avatar弹出面版dark模式
🔨 修正皮肤切回纯色模式的显示bug
➕ 增加全局公共弹窗插槽
➕ 增加皮肤列表加载效果
➕ 增加强制菜单黑色背景
➕ 增加容器组件增加stretch高度填充功能
🍹 优化皮肤切换加载效果
🔨 修正菜单badge消息通知
🔨 修正菜单badge和tag折叠后的显示问题