基于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虚拟滚动条组件)

特性

  1. 桌面路由页面支持暗黑+亮色模式
  2. 内置中英文/繁体国际化
  3. 经典桌面Dock悬浮菜单
  4. 可拖拽桌面路由+程序坞Dock菜单
  5. 桌面路由支持多个子级路由配置
  6. 动态视觉效果,自定义桌面换肤背景
  7. 可视化多窗口路由,支持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桌面多层级路由菜单
* 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桌面系统的一些分享,希望对大家有些帮助哈~~

最后附上两个最近实例项目

https://www.cnblogs.com/xiaoyan2017/p/17695193.html

https://www.cnblogs.com/xiaoyan2017/p/17552562.html

Electron-React18-MacOS桌面管理系统|electron27+react仿mac桌面的更多相关文章

  1. Electron-Vite2-MacUI桌面管理框架|electron13+vue3.x仿mac桌面UI

    基于vue3.0.11+electron13仿制macOS桌面UI管理系统ElectronVue3MacUI. 前段时间有分享一个vue3结合electron12开发后台管理系统项目.今天要分享的是最 ...

  2. electron之Windows下使用 html js css 开发桌面应用程序

    1.atom/electron github: https://github.com/atom/electron 中文文档: https://github.com/atom/electron/tree ...

  3. 使用 Flutter 开发 Mac 桌面应用

    Flutter 可以开发 Mac,Linux,Windows 桌面,但是对于平台目前只能打对于的包,以及调试本平台的包. 切换到 master 分支 首先必须切换到 master 分支.我之前在 de ...

  4. 烂泥:学习ubuntu远程桌面(二):远程桌面会话管理

    本文由秀依林枫提供友情赞助,首发于烂泥行天下 在上一篇文章中,我们讲解了如何给ubuntu安装远程桌面及其配置,这篇文章我们再来讲解下有关ubuntu远程桌面会话的问题. 一.问题描述 在我们使用ub ...

  5. Citrix 服务器虚拟化之十八 桌面虚拟化之部署MCS随机桌面池

    Citrix 服务器虚拟化之十八  桌面虚拟化之部署MCS随机桌面池 完成桌面模版的制作后,可以开始虚拟桌面池的发布 说明: 环境基于实验十七 1.登录DC服务器创建一个组织单位名为Citrix,然后 ...

  6. WIN7虚拟桌面创建(多屏幕多桌面)

    Windows7/WIN7虚拟桌面怎么用怎么创建多桌面(摘录) 在使用电脑中经常会遇到桌面软件太多了不够用的感慨,那么要是一台电脑有多个桌面就好了.在windows10中自带已经支持了虚拟桌面,在wi ...

  7. win10 常用设置 桌面出来计算机图标,固定桌面摆好的图标设置方法,电脑设备ID方法

    win10 常用设置 桌面出来计算机图标,固定桌面摆好的图标设置方法 桌面右键-->显示设置-->桌面图标设置 电脑设备ID:xxx查看方法:桌面右键-->显示设置-->关于

  8. mac远程桌面Microsoft Remote Desktop for Mac的安装与使用

    mac远程桌面Microsoft Remote Desktop for Mac的安装与使用 学习了:https://blog.csdn.net/ytangdigl/article/details/78 ...

  9. 【桌面篇】Archlinux安装kde桌面

    ArchLinux安装配置手册[桌面篇] 现在你的U盘可以拔掉了,重启后会发现和刚刚没什么区别,还是命令行的界面,别着急现在就带你安装桌面环境. 连接网络 首先检查一下网络是否连接成功 ping ww ...

  10. WPF将窗口置于桌面下方(可用于动态桌面)

    WPF将窗口置于桌面下方(可用于动态桌面) 先来看一下效果: 界面元素很简单,就一个Button按钮,然后写个定时器,定时更新Button按钮中的内容为当前时间,下面来介绍下原理,和界面组成. 窗口介 ...

随机推荐

  1. html表格基本标签

    1.<table>表签 <table>...</table>标签用于在html文档中后创建表格.它包含表名和表格本身内容的代码. 2.<tr>标签 &l ...

  2. Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-4_w0c665/PyQt5/

    错误: 解决方式:输入一下命令 1 pip3 install --upgrade setuptools 2 python3 -m pip install --upgrade pip 输入命令: 1 p ...

  3. 基于Linux的三种防火墙(IPtables、Firewall、UFW)

    学而不思则罔,思而不学则殆. 导航 IPtables Firewall UFW 对比总结 IPtables部分 1.IPtables 四表五链. 四表:filter.nat.raw.mangle. 五 ...

  4. 如何在linux上安装neovim0.9(以debian和ubuntu为例) – 东凭渭水流

    发布于 1 分钟前  3 次阅读 由于apt中只有neovim-0.72的安装包.想使用新版需要自己安装,以下是安装过程 1.首先需要卸载旧版neovim sudo remove neovim 2.从 ...

  5. Oracle数据库字符集概述及修改方式

    1.字符集概述 Oracle语言环境的描述包括三部分:language.territory.characterset(语言.地域.字符集) language:主要指定服务器消息的语言,提示信息显示中文 ...

  6. 【路由器】OpenWrt 配置使用

    目录 Web 界面 汉化 root 密码 ssh 升级 LuCI 美化 锐捷认证 MentoHUST MiniEAP 防火墙 开放端口 端口转发 IPv6 USB 安装 USB 驱动 自动挂载 Ext ...

  7. 美团面试拷打:ConcurrentHashMap 为何不能插入 null?HashMap 为何可以?

    周末的时候,有一位小伙伴提了一些关于 ConcurrentHashMap 的问题,都是他最近面试遇到的.原提问如下: 整个提问看着非常复杂,其实归纳来说就是两个问题: ConcurrentHashMa ...

  8. 《Kali渗透基础》08. 弱点扫描(二)

    @ 目录 1:OpenVAS / GVM 1.1:介绍 1.2:安装 1.3:使用 2:Nessus 2.1:介绍 2.2:安装 2.3:使用 3:Nexpose 本系列侧重方法论,各工具只是实现目标 ...

  9. CodeForces 1343E Weights Distributing

    题意 多组样例 给定\(n,m,a,b,c\),给定一个长度为\(m\)的数组\(p[]\),给定\(m\)条边,构成一个\(n\)个点\(m\)条边的无向图,\(Mike\)想要从\(a\)走到\( ...

  10. 利用RATF框架实现web状态的监控

    之前,我们已经说明了如何实现一个我们的接口测试框架RATF,当然这个框架不止可以用于管理我们的接口测试代码,我们还可以用他来对我们的web进行简单粗暴的监控. 原理: 1. 通过使用配置文件,对要监控 ...