Electron-React18-MacOS桌面管理系统|electron27+react仿mac桌面
基于React18+Electron27+ArcoDesign仿macOS桌面端系统框架ElectronMacOS。
electron-react-macOs 基于electron27.x+vite4+react18+arcoDesign+zustand等技术构建桌面版仿MacOs框架系统解决方案。支持中英文/繁体、dark+light主题、桌面多层级路由、多窗口路由页面、动态换肤、Dock悬浮菜单等功能。

ElectronReactOS系统是首创自研的桌面多层级路由菜单、支持electron多开窗口+弹窗路由窗口。

技术栈
- 开发工具:vscode
- 框架技术:vite4+react18+zustand+react-router
- 跨端技术:electron^27.0.1
- 打包工具:electron-builder^24.6.4
- UI组件库:arco-design (字节react轻量级UI组件库)
- 图表组件:bizcharts^4.1.23
- 拖拽库:sortablejs
- 模拟请求:axios
- 弹窗组件:rdialog (基于react多功能layer弹窗)
- 美化滚动条:rscroll (基于react虚拟滚动条组件)


特性
- 桌面路由页面支持暗黑+亮色模式
- 内置中英文/繁体国际化
- 经典桌面Dock悬浮菜单
- 可拖拽桌面路由+程序坞Dock菜单
- 桌面路由支持多个子级路由配置
- 动态视觉效果,自定义桌面换肤背景
- 可视化多窗口路由,支持electron新开窗口+rdialog弹窗页面

大家如果对Electron封装多窗口感兴趣,可以去看看下面这篇分享文章。
https://www.cnblogs.com/xiaoyan2017/p/17788495.html
项目结构
使用vite4构建react18项目,整合electron跨端技术,搭建桌面版OS管理系统。



















Electron桌面os布局模板
桌面os布局分为顶部操作栏+桌面端路由菜单+底部Dock菜单三大模块。

<div className="radmin__layout flexbox flex-col"> {/* 导航栏 */} <Header /> {/* 桌面区域 */} <div className="ra__layout-desktop flex1 flexbox" onContextMenu={handleDeskCtxMenu} style={{marginBottom: 70}}> <DeskMenu /> </div> {/* Dock菜单 */} <Dock /> </div>
Electron+React实现Dock菜单



底部dock菜单采用背景滤镜模糊效果、支持自适应伸缩、拖拽排序等功能。
<div className="ra__docktool"> <div className={clsx('ra__dock-wrap', !dock ? 'compact' : 'split')}> {dockMenu.map((res, key) => { return ( <div key={key} className="ra__dock-group"> { res?.children?.map((item, index) => { return ( <a key={index} className={clsx('ra__dock-item', {'active': item.active, 'filter': item.filter})} onClick={() => handleDockClick(item)}> <span className="tooltips">{item.label}</span> <div className="img"> { item.type != 'icon' ? <img src={item.image} /> : <Icon name={item.image} size={32} style={{color: 'inherit'}} /> } </div> </a> ) })} </div> ) })} </div> </div>
const dockMenu = [ { // 图片图标 children: [ {label: 'Safari', image: '/static/mac/safari.png', active: true}, {label: 'Launchpad', image: '/static/mac/launchpad.png'}, {label: 'Contacts', image: '/static/mac/contacts.png'}, {label: 'Messages', image: '/static/mac/messages.png', active: true} ] }, { // 自定义iconfont图标 children: [ {label: 'Home', image: <IconDesktop />, type: 'icon'}, {label: 'About', image: 've-icon-about', type: 'icon'} ] }, { children: [ {label: 'Appstore', image: '/static/mac/appstore.png'}, {label: 'Mail', image: '/static/mac/mail.png'}, {label: 'Maps', image: '/static/mac/maps.png', active: true}, {label: 'Photos', image: '/static/mac/photos.png'}, {label: 'Facetime', image: '/static/mac/facetime.png'}, {label: 'Calendar', image: '/static/mac/calendar.png'}, {label: 'Notes', image: '/static/mac/notes.png'}, {label: 'Calculator', image: '/static/mac/calculator.png'}, {label: 'Music', image: '/static/mac/music.png'} ] }, { children: [ {label: 'System', image: '/static/mac/system.png', active: true, filter: true}, {label: 'Empty', image: '/static/mac/bin.png', filter: true} ] } ] // 点击dock菜单 const handleDockClick = (item) => { const { label } = item if(label == 'Home') { createWin({ title: '首页', route: '/home', width: 900, height: 600 }) }else if(label == 'About') { setWinData({ type: 'CREATE_WIN_ABOUT' }) }else if(label == 'System') { createWin({ title: '网站设置', route: '/setting/system/website', isNewWin: true, width: 900, height: 600 }) } } useEffect(() => { const dockGroup = document.getElementsByClassName('ra__dock-group') // 组拖拽 for(let i = 0, len = dockGroup.length; i < len; i++) { Sortable.create(dockGroup[i], { group: 'share', handle: '.ra__dock-item', filter: '.filter', animation: 200, delay: 0, onEnd({ newIndex, oldIndex }) { console.log('新索引:', newIndex) console.log('旧索引:', oldIndex) } }) } }, [])
Electron+React桌面多级路由菜单

如上图:桌面菜单配置支持多级路由。
import { lazy } from 'react'
import {
IconDesktop, IconDashboard, IconLink, IconCommand, IconUserGroup, IconLock,
IconSafe, IconBug, IconUnorderedList, IconStop
} from '@arco-design/web-react/icon'
import Layout from '@/layouts'
import Desk from '@/layouts/desk'
import Blank from '@/layouts/blank'
import lazyload from '../lazyload'
export default [
/* 桌面模块 */
{
path: '/desk',
key: '/desk',
element: <Desk />,
meta: {
icon: <IconDesktop />,
name: 'layout__main-menu__desk',
title: 'Appstore',
isWhite: true, // 路由白名单
isAuth: true, // 需要鉴权
isHidden: false, // 是否隐藏菜单
}
},
{
path: '/home',
key: '/home',
element: <Layout>{lazyload(lazy(() => import('@views/home')))}</Layout>,
meta: {
icon: '/static/mac/appstore.png',
name: 'layout__main-menu__home-index',
title: '首页',
isAuth: true,
isNewWin: true
}
},
{
path: '/dashboard',
key: '/dashboard',
element: <Layout>{lazyload(lazy(() => import('@views/home/dashboard')))}</Layout>,
meta: {
icon: <IconDashboard />,
name: 'layout__main-menu__home-workplace',
title: '工作台',
isAuth: true
}
},
{
path: 'https://react.dev/',
key: 'https://react.dev/',
meta: {
icon: <IconLink />,
name: 'layout__main-menu__home-apidocs',
title: 'react.js官方文档',
rootRoute: '/home'
}
},
/* 组件模块 */
{
path: '/components',
key: '/components',
redirect: '/components/table/allTable', // 一级路由重定向
element: <Blank />,
meta: {
icon: <IconCommand />,
name: 'layout__main-menu__component',
title: '组件示例',
isAuth: true,
isHidden: false
},
children: [
{
path: 'table',
key: '/components/table',
element: <Blank />,
meta: {
icon: 've-icon-table',
name: 'layout__main-menu__component-table',
title: '表格',
isAuth: true
},
children: [
{
path: 'allTable',
key: '/components/table/allTable',
element: <Layout>{lazyload(lazy(() => import('@views/components/table/all')))}</Layout>,
meta: {
name: 'layout__main-menu__component-table_all',
title: '所有表格'
}
},
{
path: 'customTable',
key: '/components/table/customTable',
element: <Layout>{lazyload(lazy(() => import('@views/components/table/custom')))}</Layout>,
meta: {
name: 'layout__main-menu__component-table_custom',
title: '自定义表格'
}
},
{
path: 'search',
key: '/components/table/search',
element: <Blank />,
meta: {
name: 'layout__main-menu__component-table_search',
title: '搜索'
},
children: [
{
path: 'searchList',
key: '/components/table/search/searchList',
element: <Layout>{lazyload(lazy(() => import('@views/components/table/search')))}</Layout>,
meta: {
name: 'layout__main-menu__component-table_search_list',
title: '搜索列表'
}
}
]
}
]
},
{
path: 'list',
key: '/components/list',
element: <Layout>{lazyload(lazy(() => import('@views/components/list')))}</Layout>,
meta: {
icon: 've-icon-order-o',
name: 'layout__main-menu__component-list',
title: '列表'
}
},
{
path: 'form',
key: '/components/form',
element: <Blank />,
meta: {
icon: 've-icon-exception',
name: 'layout__main-menu__component-form',
title: '表单',
isAuth: true
},
children: [
{
path: 'allForm',
key: '/components/form/allForm',
element: <Layout>{lazyload(lazy(() => import('@views/components/form/all')))}</Layout>,
meta: {
name: 'layout__main-menu__component-form_all',
title: '所有表单'
}
},
{
path: 'customForm',
key: '/components/form/customForm',
element: <Layout>{lazyload(lazy(() => import('@views/components/form/custom')))}</Layout>,
meta: {
name: 'layout__main-menu__component-form_custom',
title: '自定义表单'
}
}
]
},
{
path: 'markdown',
key: '/components/markdown',
element: <Layout>{lazyload(lazy(() => import('@views/components/markdown')))}</Layout>,
meta: {
icon: <IconUnorderedList />,
name: 'layout__main-menu__component-markdown',
title: 'markdown编辑器'
}
},
{
path: 'qrcode',
key: '/components/qrcode',
meta: {
icon: 've-icon-qrcode',
name: 'layout__main-menu__component-qrcode',
title: '二维码'
}
},
{
path: 'print',
key: '/components/print',
meta: {
icon: 've-icon-printer',
name: 'layout__main-menu__component-print',
title: '打印'
}
},
{
path: 'pdf',
key: '/components/pdf',
meta: {
icon: 've-icon-pdffile',
name: 'layout__main-menu__component-pdf',
title: 'pdf'
}
}
]
},
/* 用户管理模块 */
{
path: '/user',
key: '/user',
redirect: '/user/userManage',
element: <Blank />,
meta: {
// icon: 've-icon-team',
icon: <IconUserGroup />,
name: 'layout__main-menu__user',
title: '用户管理',
isAuth: true,
isHidden: false
},
children: [
...
]
},
/* 配置模块 */
{
path: '/setting',
key: '/setting',
redirect: '/setting/system/website',
element: <Blank />,
meta: {
icon: 've-icon-settings-o',
name: 'layout__main-menu__setting',
title: '设置',
isHidden: false
},
children: [
...
]
},
/* 权限模块 */
{
path: '/permission',
key: '/permission',
redirect: '/permission/admin',
element: <Blank />,
meta: {
// icon: 've-icon-unlock',
icon: <IconLock />,
name: 'layout__main-menu__permission',
title: '权限管理',
isAuth: true,
isHidden: false
},
children: [
...
]
}
]
DeskMenu.jsx模板
/** * Desk桌面多层级路由菜单 * Create by andy Q:282310962 */ export default function DeskMenu() { const t = Locales() const filterRoutes = routes.filter(item => !item?.meta?.isWhite) // 桌面二级菜单弹框 const DeskPopup = (item) => { const { key, meta, children } = item return ( !meta?.isHidden && <RScroll maxHeight={220}> <div className="ra__deskmenu-popup__body"> { children.map(item => { if(item?.children) { return DeskSubMenu(item) } return DeskMenu(item) })} </div> </RScroll> ) } // 桌面菜单项 const DeskMenu = (item) => { const { key, meta, children } = item return ( !meta?.isHidden && <div key={key} className="ra__deskmenu-block"> <a className="ra__deskmenu-item" onClick={()=>handleDeskClick(item)} onContextMenu={handleDeskCtxMenu}> <div className="img"> {meta?.icon ? isImg(meta?.icon) ? <img src={meta.icon} /> : <Icon name={meta.icon} size={40} /> : <Icon name="ve-icon-file" size={40} /> } </div> { meta?.name && <span className="title clamp2">{t[meta.name]}</span> } </a> </div> ) } // 桌面二级菜单项 const DeskSubMenu = (item) => { const { key, meta, children } = item return ( !meta?.isHidden && <div key={key} className="ra__deskmenu-block"> <a className="ra__deskmenu-item group" onContextMenu={e=>e.stopPropagation()}> <Popover title={<div className="ra__deskmenu-popup__title">{meta?.name && t[meta.name]}</div>} content={() => DeskPopup(item)} trigger="hover" position="right" triggerProps={{ popupStyle: {padding: 5}, popupAlign: { right: [10, 45] }, mouseEnterDelay: 300, // showArrow: false }} style={{zIndex: 100}} > <div className="img"> {children.map((child, index) => { if(child?.meta?.isHidden) return return child?.meta?.icon ? isImg(child?.meta?.icon) ? <img key={index} src={child.meta.icon} /> : <Icon key={index} name={child.meta.icon} size={10} /> : <Icon key={index} name="ve-icon-file" size={10} /> })} </div> </Popover> { meta?.name && <span className="title clamp2">{t[meta.name]}</span> } </a> </div> ) } // 点击dock菜单 const handleDeskClick = (item) => { const { key, meta, element } = item const reg = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/ if(reg.test(key)) { window.open(key) }else { if(meta?.isNewWin) { // 新窗口打开 createWin({ title: t[meta?.name] || meta?.title, route: key, width: 900, height: 600 }) }else { // 弹窗打开 rdialog({ title: t[meta?.name] || meta?.title, content: <BrowserRouter>{element}</BrowserRouter>, maxmin: true, showConfirm: false, area: ['900px', '550px'], className: 'rc__dialogOS', customStyle: {padding: 0}, zIndex: 100 }) } } } // 右键菜单 const handleDeskCtxMenu = (e) => { e.stopPropagation() let pos = [e.clientX, e.clientY] rdialog({ type: 'contextmenu', follow: pos, opacity: .1, dialogStyle: {borderRadius: 3, overflow: 'hidden'}, btns: [ {text: '打开'}, {text: '重命名/配置'}, { text: '删除', click: () => { rdialog.close() } } ] }) } useEffect(() => { const deskEl = document.getElementById('deskSortable') Sortable.create(deskEl, { handle: '.ra__deskmenu-block', animation: 200, delay: 0, onEnd({ newIndex, oldIndex }) { console.log('新索引:', newIndex) console.log('旧索引:', oldIndex) } }) }, []) return ( <div className="ra__deskmenu" id="deskSortable"> { filterRoutes.map(item => { if(item?.children) { return DeskSubMenu(item) } return DeskMenu(item) })} </div> ) }

OK,以上就是Electron27+React18开发仿制MacOS桌面系统的一些分享,希望对大家有些帮助哈~~
最后附上两个最新Electron+vue3实例项目
Electron32-ViteOS桌面版os系统|vue3+electron+arco客户端OS管理模板
Electron31-Vue3Admin管理系统|vite5+electron+pinia桌面端后台Exe

本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)

浙公网安备 33010602011771号