React-Admin后台管理模板|react18+arco+zustand后台解决方案
<p><span style="font-size: 18px; font-family: 宋体, "Songti SC"; color: rgba(51, 102, 255, 1)">基于<span style="background-color: rgba(255, 255, 0, 1)">react18.x+vite4+arco-design</span>自研中后台管理系统解决方案<em>ReactAdmin</em>。</span></p><p><span style="font-size: 12px; font-family: "comic sans ms", sans-serif"><strong>react-vite-admin</strong> 基于vite4搭建react18.x后台管理项目。使用了<span style="background-color: rgba(204, 255, 255, 1)">react18 hooks+arco.design+zustand+bizcharts</span>等技术实现权限管理模板框架。<span style="background-color: rgba(255, 255, 153, 1)">支持暗黑/亮色主题、i18n国际化、动态权限鉴定、3种布局模板、tab路由标签栏</span>等功能。</span></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017072301856-745073673.png"></p>
<p>React18Admin管理系统是<span style="color: rgba(51, 102, 255, 1)">首创自研</span>的轻量级中后台框架,构建运行速度快,支持<strong>dark/light</strong>主题模式。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017072758983-1277186941.png"></p>
<h3>技术栈</h3>
<ul>
<li><span style="font-size: 12px">编辑器:Vscode</span></li>
<li><span style="font-size: 12px">框架技术:react18+vite4+react-router+zustand+axios</span></li>
<li><span style="font-size: 12px">组件库:arco-design (字节前端react组件库)</span></li>
<li><span style="font-size: 12px">路由管理:react-router-dom^6.16.0</span></li>
<li><span style="font-size: 12px">状态管理:zustand^4.4.1</span></li>
<li><span style="font-size: 12px">模拟数据:mockjs^1.1.0</span></li>
<li><span style="font-size: 12px">模拟请求:axios^1.5.1</span></li>
<li><span style="font-size: 12px">图表库:bizcharts^4.1.22</span></li>
<li><span style="font-size: 12px">编辑器组件:@wangeditor/editor-for-react^1.0.6</span></li>
<li><span style="font-size: 12px">markdown编辑器:@uiw/react-md-editor^3.23.6</span></li>
<li><span style="font-size: 12px">请求进度插件:nprogress^0.2.0</span></li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017074532601-470803765.gif"></p>
<p>react-admin采用字节出品的react桌面端组件库<strong>arco.design</strong>。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017074924970-272121440.png"></p>
<h3>特性</h3>
<ol>
<li><span style="font-size: 12px">基于vite4.x构建react18后台,轻/快/小</span></li>
<li><span style="font-size: 12px">使用最新前端技术栈react18、zustand、bizcharts、react-router、axios</span></li>
<li><span style="font-size: 12px">搭配清新react组件库arco.design</span></li>
<li><span style="font-size: 12px">支持中英文/繁体国际化语言</span></li>
<li><span style="font-size: 12px">支持动态路由权限验证</span></li>
<li><span style="font-size: 12px">支持动态tabs标签栏控制</span></li>
<li><span style="font-size: 12px">内置多种模板布局样式</span></li>
</ol>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017075854606-1002673853.gif"></p>
<blockquote>
<h3><span style="background-color: rgba(255, 255, 153, 1); color: rgba(128, 0, 0, 1)">目前该react-admin后台管理模板,已经更新到我的原创作品集。</span></h3>
<p><span style="font-size: 12px">自研React18+ArcoDesign网页版后台管理admin系统</span></p>
</blockquote>
<h3>项目结构目录</h3>
<p>采用标准化的react目录结构,整个项目使用<em><strong>react18 function</strong></em>语法编码开发。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081241955-2073966326.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081356819-1165799555.gif"></p>
<h3>构建预览图</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081454676-1367984894.gif"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081821809-520372854.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081553638-310822205.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081609079-240870084.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081844916-1369157975.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081920444-1493595489.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017081951565-362686758.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082014973-299907662.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082033627-1125072999.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082122223-113626874.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082151929-429513539.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082212471-1387548700.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082233874-1211621189.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082325791-480967834.png"></p>
<p><em>wangeditor-react</em>图文编辑器使用的是wangeditor的<strong>react</strong>版本,支持dark/light主题。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082347086-1540104961.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082443063-546347954.png"></p>
<p><em>react-md-editor</em>基于react的<strong>markdown编辑器</strong>,支持dark/light主题。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082719297-602930521.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017082733597-1173436593.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083051336-1415793202.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083137096-1269808678.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083206744-605408524.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083224231-2133513997.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083248150-1965682863.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083311275-2012365437.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017083332417-2144562792.png"></p>
<p>react18-scrollbar项目中使用的虚拟滚动条基于react18自定义组件实现功能。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017084324952-159579079.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">// 引入滚动条组件
import RScroll from '@/components/rscroll'
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">RScroll </span><span style="color: rgba(255, 0, 0, 1)">autohide maxHeight</span><span style="color: rgba(0, 0, 255, 1)">={100}</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
包裹需要滚动的内容块。。。
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">RScroll</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<h3>React18-Admin布局模板</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017084519942-581809280.png"></p>
<p>如上图:支持<strong>分栏+垂直+水平</strong>3种通用布局模板。也可以定制化模板样式。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017084804366-1526972436.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 主布局模板
* @author Hs Q:282310962
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { useMemo } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import { appStore } from </span>'@/store/app'<span style="color: rgba(0, 0, 0, 1)">
import Columns from </span>'./template/columns'<span style="color: rgba(0, 0, 0, 1)">
import Vertical from </span>'./template/vertical'<span style="color: rgba(0, 0, 0, 1)">
import Transverse from </span>'./template/transverse'
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Layout() {
const { config: { skin, layout } } </span>=<span style="color: rgba(0, 0, 0, 1)"> appStore()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 布局模板</span>
const LayoutComponent = useMemo(() =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">(layout) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'columns'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Columns
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'vertical'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Vertical
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'transverse'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Transverse
</span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Columns
}
}, )
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className="radmin__container">
<LayoutComponent />
</div>
<span style="color: rgba(0, 0, 0, 1)"> )
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Layout</pre>
</div>
<p>主模板Main.jsx动态<strong>Permission鉴权</strong>验证。</p>
<div class="cnblogs_code">
<pre>import './index.scss'<span style="color: rgba(0, 0, 0, 1)">
import { Outlet } from </span>'react-router-dom'<span style="color: rgba(0, 0, 0, 1)">
import RScroll from </span>'@/components/rscroll'<span style="color: rgba(0, 0, 0, 1)">
import Permission from </span>'@/components/Permission'<span style="color: rgba(0, 0, 0, 1)">
import Forbidden from </span>'@/views/error/forbidden'<span style="color: rgba(0, 0, 0, 1)">
import { useRoute } from </span>'@/hooks/useRoutes'<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Main() {
const route </span>=<span style="color: rgba(0, 0, 0, 1)"> useRoute()
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><>
<RScroll>
<div className="ra__layout-main__wrapper"><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 鉴权组件 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><<span style="color: rgba(0, 0, 0, 1)">Permission
roles</span>={route?.meta?<span style="color: rgba(0, 0, 0, 1)">.roles}
content</span>={<Forbidden />}
>
<Outlet />
</Permission>
</div>
</RScroll>
</>
<span style="color: rgba(0, 0, 0, 1)"> )
}</span></pre>
</div>
<h3>react-router路由配置</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017090936959-1596601401.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* @title react-router-dom v6路由配置管理
* @author andy
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { useRoutes, Navigate } from </span>'react-router-dom'<span style="color: rgba(0, 0, 0, 1)">
import Error from </span>'@views/error/404'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 批量导入modules路由</span>
const modules = import.meta.glob('./modules/*.jsx', { eager: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> })
const patchRoutes </span>= Object.keys(modules).map(key => modules.<span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">).flat()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> useRoutes集中式路由配置</span>
export const routes =<span style="color: rgba(0, 0, 0, 1)"> [
{
path: </span>'/'<span style="color: rgba(0, 0, 0, 1)">,
element: </span><Navigate to="/home" replace={<span style="color: rgba(0, 0, 255, 1)">true</span>} />,
<span style="color: rgba(0, 0, 0, 1)"> meta: {
isWhite: </span><span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 路由白名单</span>
<span style="color: rgba(0, 0, 0, 1)"> }
},
...patchRoutes,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 404模块 path="*"不能省略</span>
<span style="color: rgba(0, 0, 0, 1)"> {
path: </span>'*'<span style="color: rgba(0, 0, 0, 1)">,
element: </span><Error />,
<span style="color: rgba(0, 0, 0, 1)"> meta: {
isWhite: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
}
]
const Router </span>= () =><span style="color: rgba(0, 0, 0, 1)"> useRoutes(routes)
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Router</pre>
</div>
<p><strong>lazyload.jsx配置</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 延迟加载提示
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { Suspense } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import { Spin } from </span>'@arco-design/web-react'<span style="color: rgba(0, 0, 0, 1)">
import NprogressLoading from </span>'./nprogress'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加载提示</span>
const SpinLoading = () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><<span style="color: rgba(0, 0, 0, 1)">Spin
tip</span>='loading...'<span style="color: rgba(0, 0, 0, 1)">
style</span>=<span style="color: rgba(0, 0, 0, 1)">{{
width: </span>'100%'<span style="color: rgba(0, 0, 0, 1)">
}}
</span>/>
<span style="color: rgba(0, 0, 0, 1)"> )
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 延迟加载</span>
const lazyload = LazyComponent =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> React 16.6 新增了<Suspense>组件,懒加载的模式需要我们给他加上一层 Loading的提示加载组件</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> return <Suspense fallback={<SpinLoading />}><LazyComponent /></Suspense></span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <Suspense fallback={<NprogressLoading />}><LazyComponent /></Suspense>
<span style="color: rgba(0, 0, 0, 1)">}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> lazyload</pre>
</div>
<p><strong>NProgress.jsx配置</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 加载进度条NProgress
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { Component } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import NProgress from </span>'nprogress'<span style="color: rgba(0, 0, 0, 1)">
import </span>'nprogress/nprogress.css'<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class NprogressLoading extends Component {
constructor(props) {
super(props)
NProgress.set(.</span>4<span style="color: rgba(0, 0, 0, 1)">)
NProgress.start()
}
componentDidMount() {
NProgress.done()
}
render() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <div />
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<p><strong>主路由main.jsx配置</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 主路由配置
* @author Hs
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { lazy } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import {
IconHome, IconDashboard, IconLink, IconCommand, IconUserGroup, IconLock,
IconMenu, IconSafe, IconBug, IconHighlight, IconUnorderedList, IconStop
} from </span>'@arco-design/web-react/icon'<span style="color: rgba(0, 0, 0, 1)">
import Layout from </span>'@/layouts'<span style="color: rgba(0, 0, 0, 1)">
import Blank from </span>'@/layouts/blank'<span style="color: rgba(0, 0, 0, 1)">
import lazyload from </span>'../lazyload'<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">首页模块</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
{
path: </span>'/home'<span style="color: rgba(0, 0, 0, 1)">,
key: </span>'/home', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用于Menu组件跳转路由地址</span>
element: <Layout />,
<span style="color: rgba(0, 0, 0, 1)"> meta: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> icon: 've-icon-home', // 菜单图标</span>
icon: <IconHome />,
name: 'layout__main-menu__home', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> i18n国际化标题</span>
title: '主页'<span style="color: rgba(0, 0, 0, 1)">,
isAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 需要鉴权</span>
isHidden: <span style="color: rgba(0, 0, 255, 1)">false</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否隐藏菜单</span>
isAffix: <span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 固定tabview标签栏(不可关闭)</span>
<span style="color: rgba(0, 0, 0, 1)"> },
children: [
{
key: </span>'/home'<span style="color: rgba(0, 0, 0, 1)">,
index: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
element: lazyload(lazy(() </span>=> import('@views/home'<span style="color: rgba(0, 0, 0, 1)">))),
meta: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> icon: 've-icon-home',</span>
icon: <IconHome />,
name: 'layout__main-menu__home-index'<span style="color: rgba(0, 0, 0, 1)">,
title: </span>'首页'<span style="color: rgba(0, 0, 0, 1)">,
isAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 工作台</span>
<span style="color: rgba(0, 0, 0, 1)"> {
path: </span>'dashboard'<span style="color: rgba(0, 0, 0, 1)">,
key: </span>'/home/dashboard'<span style="color: rgba(0, 0, 0, 1)">,
element: lazyload(lazy(() </span>=> import('@views/home/dashboard'<span style="color: rgba(0, 0, 0, 1)">))),
meta: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> icon: 've-icon-computer',</span>
icon: <IconDashboard />,
name: 'layout__main-menu__home-workplace'<span style="color: rgba(0, 0, 0, 1)">,
title: </span>'工作台'<span style="color: rgba(0, 0, 0, 1)">,
isAuth: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 外部链接</span>
<span style="color: rgba(0, 0, 0, 1)"> {
path: </span>'https://react.dev/'<span style="color: rgba(0, 0, 0, 1)">,
key: </span>'https://react.dev/'<span style="color: rgba(0, 0, 0, 1)">,
meta: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> icon: 've-icon-clip',</span>
icon: <IconLink />,
name: 'layout__main-menu__home-apidocs'<span style="color: rgba(0, 0, 0, 1)">,
title: </span>'react.js官方文档'<span style="color: rgba(0, 0, 0, 1)">,
rootRoute: </span>'/home'<span style="color: rgba(0, 0, 0, 1)">
}
}
]
},
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">组件模块</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
{
...
},
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">用户管理模块</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
{
...
},
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">权限模块</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
{
...
},
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">错误模块</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
{
...
}
]</span></pre>
</div>
<p><strong><span style="font-size: 16px; font-family: "Microsoft YaHei""><em><span style="color: rgba(255, 0, 0, 1)">备注:路由菜单参数配置说明。</span></em></span></strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* @description 路由参数说明
* @param path ==> 路由地址标识
* @param key ==> 用于Menu组件跳转路由地址
* @param redirect ==> 重定向地址
* @param element ==> 视图页面路径
* 菜单信息(meta)
* @param meta.icon ==> 菜单图标
* @param meta.title ==> 菜单标题
* @param meta.name ==> i18n国际化标题
* @param meta.roles ==> 页面权限 ['admin', 'dev', 'test']
* @param meta.isAuth ==> 是否需要验证
* @param meta.isHidden ==> 是否隐藏页面
* @param meta.isAffix ==> 是否固定标签(tabs标签栏不能关闭)
* </span><span style="color: rgba(0, 128, 0, 1)">*/</span></pre>
</div>
<h3>react自定义路由菜单Menu</h3>
<p>基于arco.design组件库提供的Menu组件封装三种不同的路由菜单。</p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017085724006-761176492.png"> <img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017085837809-142280797.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017090103836-614971786.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">RouteMenu </span><span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">RouteMenu </span><span style="color: rgba(255, 0, 0, 1)">rootRouteEnable </span><span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">RouteMenu </span><span style="color: rgba(255, 0, 0, 1)">rootRouteEnable mode</span><span style="color: rgba(0, 0, 255, 1)">="horizontal"</span> <span style="color: rgba(0, 0, 255, 1)">/></span></pre>
</div>
<p><strong>RouteMenu路由菜单模板</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">/**
* 路由菜单模板
*/
import './index.scss'
import { useState, useMemo, useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { Menu } from '@arco-design/web-react'
import Icon from '@components/Icon'
import RouteSubMenu from './submenu'
import { routes } from '@/routers'
import { getCurrentRootRoute, findParentRoute } from '@/hooks/useRoutes'
import Locales from '@/locales'
export default function RouteMenu(props) {
const {
// 菜单类型(垂直vertical 水平菜单horizontal 弹出pop)
mode = 'vertical',
// 菜单风格('light' | 'dark')
theme = 'light',
// 是否开启一级路由菜单
rootRouteEnable = false,
style = {}
} = props
const navigate = useNavigate()
const { pathname } = useLocation()
const t = Locales()
const = useState([])
const rootRoute = getCurrentRootRoute()
const filterRoutes = routes.filter(item => !item?.meta?.isWhite)
const menuRoutes = useMemo(() => {
if(rootRouteEnable) {
return filterRoutes
}
// 过滤一级菜单
return filterRoutes.find(item => item.path == rootRoute && item.children)?.children
}, )
useEffect(() => {
setOpenKeys(getKeys(pathname))
}, )
// 获取选中菜单路由keys数组
const getKeys = (key) => {
return findParentRoute(menuRoutes, key)?.map(item => item?.key)
}
const handleNavigate = (key) => {
const reg = /[-a-zA-Z0-9]{0,62}(\.[-a-zA-Z0-9]{0,62})+\.?/
if(reg.test(key)) {
window.open(key)
}else {
navigate(key)
}
}
return (
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu
</span><span style="color: rgba(255, 0, 0, 1)">className</span><span style="color: rgba(0, 0, 255, 1)">="ra__menus"</span><span style="color: rgba(255, 0, 0, 1)">
mode</span><span style="color: rgba(0, 0, 255, 1)">={mode}
</span><span style="color: rgba(255, 0, 0, 1)">theme</span><span style="color: rgba(0, 0, 255, 1)">={theme}
</span><span style="color: rgba(255, 0, 0, 1)">selectedKeys</span><span style="color: rgba(0, 0, 255, 1)">={}
</span><span style="color: rgba(255, 0, 0, 1)">openKeys</span><span style="color: rgba(0, 0, 255, 1)">={openKeys}
</span><span style="color: rgba(255, 0, 0, 1)">levelIndent</span><span style="color: rgba(0, 0, 255, 1)">={28}
</span><span style="color: rgba(255, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">={{ </span><span style="color: rgba(255, 0, 0, 1)">...style }}
onClickMenuItem</span><span style="color: rgba(0, 0, 255, 1)">={handleNavigate}
</span><span style="color: rgba(255, 0, 0, 1)">onClickSubMenu</span><span style="color: rgba(0, 0, 255, 1)">={(_, </span><span style="color: rgba(255, 0, 0, 1)">openKeys) </span><span style="color: rgba(0, 0, 255, 1)">=</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> {
setOpenKeys(openKeys)
}}
>
{ menuRoutes.map(item => {
if(item?.children) {
return RouteSubMenu(item, t)
}
return (
!item?.meta?.isHidden &&
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item className</span><span style="color: rgba(0, 0, 255, 1)">="ra__menuItem"</span><span style="color: rgba(255, 0, 0, 1)"> key</span><span style="color: rgba(0, 0, 255, 1)">={item.redirect </span><span style="color: rgba(255, 0, 0, 1)">|| item.key}</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
{ item?.meta?.icon && </span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">={item.meta.icon} </span><span style="color: rgba(255, 0, 0, 1)">size</span><span style="color: rgba(0, 0, 255, 1)">={18} </span><span style="color: rgba(255, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">={{marginRight: </span><span style="color: rgba(255, 0, 0, 1)">10}} </span><span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 0, 0, 1)"> }
{ item?.meta?.name && </span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span>{t}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
)
})}
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
)
}</span></pre>
</div>
<h3>react18状态管理zustand</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017093057983-1460989159.png"></p>
<p><em><strong>Zustand</strong></em>新一代react状态管理工具,内置多种插件,支持<strong>persist</strong>本地存储服务。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* react18状态管理库Zustand4,中间件persist本地持久化存储
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { create } from </span>'zustand'<span style="color: rgba(0, 0, 0, 1)">
import { persist, createJSONStorage } from </span>'zustand/middleware'<span style="color: rgba(0, 0, 0, 1)">
import { generate, getRgbStr } from </span>'@arco-design/color'<span style="color: rgba(0, 0, 0, 1)">
export const appStore </span>=<span style="color: rgba(0, 0, 0, 1)"> create(
persist(
(set, get) </span>=><span style="color: rgba(0, 0, 0, 1)"> ({
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 语言(中文zh-CN 英文en 繁体字zh-TW)</span>
lang: 'zh-CN'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 角色类型 roles: ['admin'] / roles: ['admin', 'dev'] / roles: ['dev', test']</span>
roles: ["dev"<span style="color: rgba(0, 0, 0, 1)">],
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置信息</span>
<span style="color: rgba(0, 0, 0, 1)"> config: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 布局(分栏columns 纵向vertical 横向transverse)</span>
layout: 'columns'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 模式(亮色light - 暗黑dark)</span>
mode: 'light'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主题色</span>
theme: '#3491FA'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否折叠菜单</span>
collapsed: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启面包屑导航</span>
breadcrumb: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开启标签栏</span>
tabsview: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
tabRoutes: [],
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示搜索</span>
showSearch: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示全屏</span>
showFullscreen: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示语言</span>
showLang: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示公告</span>
showNotice: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示底部</span>
showFooter: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新配置</span>
updateConfig: (key, value) =><span style="color: rgba(0, 0, 0, 1)"> set({
config: { ...get().config, : value }
}),
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置角色</span>
setRoles: (roles) =><span style="color: rgba(0, 0, 0, 1)"> set({roles}),
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置多语言</span>
setLang: (lang) =><span style="color: rgba(0, 0, 0, 1)"> set({lang}),
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置主题模式</span>
setMode: (mode) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(mode == 'dark'<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置为暗黑主题</span>
document.body.setAttribute('arco-theme', 'dark'<span style="color: rgba(0, 0, 0, 1)">)
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 恢复亮色主题</span>
document.body.removeAttribute('arco-theme'<span style="color: rgba(0, 0, 0, 1)">)
}
get().updateConfig(</span>'mode'<span style="color: rgba(0, 0, 0, 1)">, mode)
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置主题样式</span>
setTheme: (theme) =><span style="color: rgba(0, 0, 0, 1)"> {
const colors </span>= generate(theme, { list: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> })
colors.map((item, index) </span>=><span style="color: rgba(0, 0, 0, 1)"> {
const rgbStr </span>=<span style="color: rgba(0, 0, 0, 1)"> getRgbStr(item)
document.body.style.setProperty(`</span>--arcoblue-${index + 1<span style="color: rgba(0, 0, 0, 1)">}`, rgbStr)
})
get().updateConfig(</span>'theme'<span style="color: rgba(0, 0, 0, 1)">, theme)
}
}),
{
name: </span>'appState'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> name: 'app-store', // name of the item in the storage (must be unique)</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> storage: createJSONStorage(() => sessionStorage), // by default, 'localStorage'</span>
<span style="color: rgba(0, 0, 0, 1)"> }
)
)</span></pre>
</div>
<h3>react18国际化配置i18n</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017093750196-2139331344.png"></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017093850844-390073734.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 国际化配置
* @author YXY
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { appStore } from </span>'@/store/app'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入语言配置</span>
import enUS from './en-US'<span style="color: rgba(0, 0, 0, 1)">
import zhCN from </span>'./zh-CN'<span style="color: rgba(0, 0, 0, 1)">
import zhTW from </span>'./zh-TW'<span style="color: rgba(0, 0, 0, 1)">
export const locales </span>=<span style="color: rgba(0, 0, 0, 1)"> {
</span>'en'<span style="color: rgba(0, 0, 0, 1)">: enUS,
</span>'zh-CN'<span style="color: rgba(0, 0, 0, 1)">: zhCN,
</span>'zh-TW'<span style="color: rgba(0, 0, 0, 1)">: zhTW
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> (locale) =><span style="color: rgba(0, 0, 0, 1)"> {
const appState </span>=<span style="color: rgba(0, 0, 0, 1)"> appStore()
const lang </span>= appState.lang || 'zh-CN'
<span style="color: rgba(0, 0, 255, 1)">return</span> (locale || locales) ||<span style="color: rgba(0, 0, 0, 1)"> {}
}</span></pre>
</div>
<p>App.jsx引入arco.design组件库语言包。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 入口模板
* @author Hs
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import { useEffect, useMemo } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import { HashRouter } from </span>'react-router-dom'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过 ConfigProvider 组件实现国际化</span>
import { ConfigProvider } from '@arco-design/web-react'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入语言包</span>
import enUS from '@arco-design/web-react/es/locale/en-US'<span style="color: rgba(0, 0, 0, 1)">
import zhCN from </span>'@arco-design/web-react/es/locale/zh-CN'<span style="color: rgba(0, 0, 0, 1)">
import zhTW from </span>'@arco-design/web-react/es/locale/zh-TW'<span style="color: rgba(0, 0, 0, 1)">
import { AuthRouter } from </span>'@/hooks/useRoutes'<span style="color: rgba(0, 0, 0, 1)">
import { appStore } from </span>'@/store/app'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入路由配置</span>
import Router from './routers'
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
const { lang, config: { mode, theme }, setMode, setTheme } </span>=<span style="color: rgba(0, 0, 0, 1)"> appStore()
const locale </span>= useMemo(() =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">(lang) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'en'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> enUS
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'zh-CN'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> zhCN
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'zh-TW'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> zhTW
</span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> zhCN
}
}, )
useEffect(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
setMode(mode)
setTheme(theme)
}, [])
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><ConfigProvider locale={locale}>
<HashRouter>
<AuthRouter>
<Router />
</AuthRouter>
</HashRouter>
</ConfigProvider>
<span style="color: rgba(0, 0, 0, 1)"> )
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> App</pre>
</div>
<p><strong>Lang.jsx配置</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">import { Dropdown, Menu, Button } from '@arco-design/web-react'
import Icon from '@components/Icon'
import { appStore } from '@/store/app'
export default function Lang() {
const { lang, setLang } = appStore()
const handleLang = val => {
setLang(val)
}
return (
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Dropdown
</span><span style="color: rgba(255, 0, 0, 1)">position</span><span style="color: rgba(0, 0, 255, 1)">="bottom"</span><span style="color: rgba(255, 0, 0, 1)">
droplist</span><span style="color: rgba(0, 0, 255, 1)">={
</span><span style="color: rgba(255, 0, 0, 1)"><Menu className</span><span style="color: rgba(0, 0, 255, 1)">="radmin__dropdownLang"</span><span style="color: rgba(255, 0, 0, 1)"> defaultSelectedKeys</span><span style="color: rgba(0, 0, 255, 1)">={} </span><span style="color: rgba(255, 0, 0, 1)">onClickMenuItem</span><span style="color: rgba(0, 0, 255, 1)">={handleLang}</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">='zh-CN'</span><span style="color: rgba(0, 0, 255, 1)">></span>简体中文 <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span>zh-CN<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="zh-TW"</span><span style="color: rgba(0, 0, 255, 1)">></span>繁体字 <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span>zh-TW<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="en"</span><span style="color: rgba(0, 0, 255, 1)">></span>英文 <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span>en<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
}
>
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Button
</span><span style="color: rgba(255, 0, 0, 1)">shape</span><span style="color: rgba(0, 0, 255, 1)">="circle"</span><span style="color: rgba(255, 0, 0, 1)">
size</span><span style="color: rgba(0, 0, 255, 1)">="small"</span><span style="color: rgba(255, 0, 0, 1)">
icon</span><span style="color: rgba(0, 0, 255, 1)">={<Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-lang"</span> <span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 0, 0, 1)">}
/>
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Dropdown</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
)
}</span></pre>
</div>
<h3>Tabs.jsx动态路由栏</h3>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017094809954-295333064.png"></p>
<p>项目中动态路由栏tabs采用arco.design组件库Tabs组件自定义实现功能。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Tabs
</span><span style="color: rgba(255, 0, 0, 1)">activeTab</span><span style="color: rgba(0, 0, 255, 1)">={pathname}
</span><span style="color: rgba(255, 0, 0, 1)">editable
showAddButton</span><span style="color: rgba(0, 0, 255, 1)">={false}
</span><span style="color: rgba(255, 0, 0, 1)">onDeleteTab</span><span style="color: rgba(0, 0, 255, 1)">={key =</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> delTabs(key)}
>
{ tabRoutes.map(item => (
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Tabs</span><span style="color: rgba(255, 0, 0, 1)">.TabPane
closable</span><span style="color: rgba(0, 0, 255, 1)">={!item?.meta?.isAffix}
</span><span style="color: rgba(255, 0, 0, 1)">key</span><span style="color: rgba(0, 0, 255, 1)">={item?.redirect </span><span style="color: rgba(255, 0, 0, 1)">|| item?.key}
title</span><span style="color: rgba(0, 0, 255, 1)">={
</span><span style="color: rgba(255, 0, 0, 1)"><Dropdown
trigger</span><span style="color: rgba(0, 0, 255, 1)">='contextMenu'
</span><span style="color: rgba(255, 0, 0, 1)">position</span><span style="color: rgba(0, 0, 255, 1)">='bl'
</span><span style="color: rgba(255, 0, 0, 1)">droplist</span><span style="color: rgba(0, 0, 255, 1)">={
</span><span style="color: rgba(255, 0, 0, 1)"><Menu className</span><span style="color: rgba(0, 0, 255, 1)">="ra__dropdownContext"</span><span style="color: rgba(255, 0, 0, 1)"> onClickMenuItem</span><span style="color: rgba(0, 0, 255, 1)">={(key, </span><span style="color: rgba(255, 0, 0, 1)">e) </span><span style="color: rgba(0, 0, 255, 1)">=</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> handleClickMenuItem(key, e, item)}>
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="close"</span><span style="color: rgba(255, 0, 0, 1)"> disabled</span><span style="color: rgba(0, 0, 255, 1)">={item?.meta?.isAffix}</span><span style="color: rgba(0, 0, 255, 1)">><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-close"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>{t['tabview__contextmenu-close']}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="closeLeft"</span><span style="color: rgba(255, 0, 0, 1)"> disabled</span><span style="color: rgba(0, 0, 255, 1)">={isFirstTab()}</span><span style="color: rgba(0, 0, 255, 1)">><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-prev"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>{t['tabview__contextmenu-closeleft']}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="closeRight"</span><span style="color: rgba(255, 0, 0, 1)"> disabled</span><span style="color: rgba(0, 0, 255, 1)">={isLastTab()}</span><span style="color: rgba(0, 0, 255, 1)">><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-next"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>{t['tabview__contextmenu-closeright']}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="closeOther"</span><span style="color: rgba(0, 0, 255, 1)">><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-reset"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>{t['tabview__contextmenu-closeother']}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(255, 0, 0, 1)">.Item key</span><span style="color: rgba(0, 0, 255, 1)">="closeAll"</span><span style="color: rgba(0, 0, 255, 1)">><</span><span style="color: rgba(128, 0, 0, 1)">Icon </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="ve-icon-close-circle-o"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>{t['tabview__contextmenu-closeall']}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu.Item</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Menu</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
}
onVisibleChange={visible=>handleOpenContextMenu(visible, item)}
>
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">span </span><span style="color: rgba(255, 0, 0, 1)">className</span><span style="color: rgba(0, 0, 255, 1)">="ra__tabsview-title"</span><span style="color: rgba(255, 0, 0, 1)"> onClick</span><span style="color: rgba(0, 0, 255, 1)">={() =</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> navigate(item?.redirect || item?.key)}>
</span><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">TabIcon </span><span style="color: rgba(255, 0, 0, 1)">path</span><span style="color: rgba(0, 0, 255, 1)">={item?.key} </span><span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 0, 0, 1)">
{ t }
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Dropdown</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)">
}
/>
))}
</span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">Tabs</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<div class="cnblogs_code">
<pre>export <span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> TabsView() {
const { pathname } </span>=<span style="color: rgba(0, 0, 0, 1)"> useLocation()
const navigate </span>=<span style="color: rgba(0, 0, 0, 1)"> useNavigate()
const </span>=<span style="color: rgba(0, 0, 0, 1)"> useState()
const { config: { tabRoutes }, updateConfig } </span>=<span style="color: rgba(0, 0, 0, 1)"> appStore()
const route </span>=<span style="color: rgba(0, 0, 0, 1)"> useRoute()
const t </span>=<span style="color: rgba(0, 0, 0, 1)"> Locales()
useEffect(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
addTabs()
}, )
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加</span>
const addTabs = () =><span style="color: rgba(0, 0, 0, 1)"> {
const tabIndex </span>= tabRoutes.findIndex(item => item?.key ===<span style="color: rgba(0, 0, 0, 1)"> pathname)
let newTabs </span>=<span style="color: rgba(0, 0, 0, 1)"> tabRoutes
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(tabIndex == -1<span style="color: rgba(0, 0, 0, 1)">) {
newTabs.push(route)
}
newTabs.map(item </span>=><span style="color: rgba(0, 0, 0, 1)"> {
item.isActive </span>= <span style="color: rgba(0, 0, 255, 1)">false</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(item?.key ===<span style="color: rgba(0, 0, 0, 1)"> pathname) {
item.isActive </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
})
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除</span>
const delTabs = (path) =><span style="color: rgba(0, 0, 0, 1)"> {
const tabIndex </span>= tabRoutes.findIndex(item => item?.key ===<span style="color: rgba(0, 0, 0, 1)"> path)
let newTabs </span>=<span style="color: rgba(0, 0, 0, 1)"> tabRoutes
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(tabIndex > -1<span style="color: rgba(0, 0, 0, 1)">) {
newTabs.splice(tabIndex, </span>1<span style="color: rgba(0, 0, 0, 1)">)
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
updateTabs(newTabs)
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除左侧标签</span>
const delLeftTabs = (path) =><span style="color: rgba(0, 0, 0, 1)"> {
const tabIndex </span>= tabRoutes.findIndex(item => item?.key ===<span style="color: rgba(0, 0, 0, 1)"> path)
let newTabs </span>=<span style="color: rgba(0, 0, 0, 1)"> tabRoutes
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(tabIndex > -1<span style="color: rgba(0, 0, 0, 1)">) {
newTabs </span>= newTabs.filter((item, i) => item?.meta?.isAffix || i >=<span style="color: rgba(0, 0, 0, 1)"> tabIndex)
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
updateTabs(newTabs)
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除右侧标签</span>
const delRightTabs = (path) =><span style="color: rgba(0, 0, 0, 1)"> {
const tabIndex </span>= tabRoutes.findIndex(item => item?.key ===<span style="color: rgba(0, 0, 0, 1)"> path)
let newTabs </span>=<span style="color: rgba(0, 0, 0, 1)"> tabRoutes
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(tabIndex > -1<span style="color: rgba(0, 0, 0, 1)">) {
newTabs </span>= newTabs.filter((item, i) => item?.meta?.isAffix || i <=<span style="color: rgba(0, 0, 0, 1)"> tabIndex)
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
updateTabs(newTabs)
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除其它</span>
const delOtherTabs = (path) =><span style="color: rgba(0, 0, 0, 1)"> {
let newTabs </span>= tabRoutes.filter(item => item?.meta?.isAffix || item?.key ===<span style="color: rgba(0, 0, 0, 1)"> path)
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
updateTabs(newTabs)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除所有</span>
const delAllTabs = () =><span style="color: rgba(0, 0, 0, 1)"> {
let newTabs </span>= tabRoutes.filter(item => item?.meta?<span style="color: rgba(0, 0, 0, 1)">.isAffix)
updateConfig(</span>'tabRoutes'<span style="color: rgba(0, 0, 0, 1)">, newTabs)
updateTabs(newTabs)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新跳转选项卡</span>
const updateTabs = (tabs) =><span style="color: rgba(0, 0, 0, 1)"> {
const nextTab </span>= tabs || tabs
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!nextTab) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
navigate(nextTab</span>?.redirect || nextTab?<span style="color: rgba(0, 0, 0, 1)">.key)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否第一个标签</span>
const isFirstTab = () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> selectedTab?.key === tabRoutes?.key || selectedTab?.key === '/home'<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否最后一个标签</span>
const isLastTab = () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> selectedTab?.key === tabRoutes?<span style="color: rgba(0, 0, 0, 1)">.key
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开右键菜单</span>
const handleOpenContextMenu = (visible, item) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(visible) {
setSelectedTab(item)
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 点击右键菜单项</span>
const handleClickMenuItem = (key, e, item) =><span style="color: rgba(0, 0, 0, 1)"> {
e.stopPropagation()
const path </span>= item?<span style="color: rgba(0, 0, 0, 1)">.key
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">(key) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'close'<span style="color: rgba(0, 0, 0, 1)">:
delTabs(path)
</span><span style="color: rgba(0, 0, 255, 1)">break</span>
<span style="color: rgba(0, 0, 255, 1)">case</span> 'closeLeft'<span style="color: rgba(0, 0, 0, 1)">:
delLeftTabs(path)
</span><span style="color: rgba(0, 0, 255, 1)">break</span>
<span style="color: rgba(0, 0, 255, 1)">case</span> 'closeRight'<span style="color: rgba(0, 0, 0, 1)">:
delRightTabs(path)
</span><span style="color: rgba(0, 0, 255, 1)">break</span>
<span style="color: rgba(0, 0, 255, 1)">case</span> 'closeOther'<span style="color: rgba(0, 0, 0, 1)">:
delOtherTabs(path)
</span><span style="color: rgba(0, 0, 255, 1)">break</span>
<span style="color: rgba(0, 0, 255, 1)">case</span> 'closeAll'<span style="color: rgba(0, 0, 0, 1)">:
delAllTabs()
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
...
)
}</span></pre>
</div>
<p>OK,以上就是react18+arco开发后台系统的一些分享,希望对大家有所帮助~ 💖</p>
<p><strong>最后附上两个最新项目实例</strong></p>
<p><span style="font-size: 12px">https://www.cnblogs.com/xiaoyan2017/p/17630296.html</span></p>
<p><span style="font-size: 12px">https://www.cnblogs.com/xiaoyan2017/p/17468074.html</span></p>
<p><img src="https://img2023.cnblogs.com/blog/1289798/202310/1289798-20231017100003691-537573191.gif"></p>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)<br><br>
来源:https://www.cnblogs.com/xiaoyan2017/p/17769058.html
頁:
[1]