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桌面多层级路由菜单
* 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桌面的更多相关文章
- Electron-Vite2-MacUI桌面管理框架|electron13+vue3.x仿mac桌面UI
基于vue3.0.11+electron13仿制macOS桌面UI管理系统ElectronVue3MacUI. 前段时间有分享一个vue3结合electron12开发后台管理系统项目.今天要分享的是最 ...
- electron之Windows下使用 html js css 开发桌面应用程序
1.atom/electron github: https://github.com/atom/electron 中文文档: https://github.com/atom/electron/tree ...
- 使用 Flutter 开发 Mac 桌面应用
Flutter 可以开发 Mac,Linux,Windows 桌面,但是对于平台目前只能打对于的包,以及调试本平台的包. 切换到 master 分支 首先必须切换到 master 分支.我之前在 de ...
- 烂泥:学习ubuntu远程桌面(二):远程桌面会话管理
本文由秀依林枫提供友情赞助,首发于烂泥行天下 在上一篇文章中,我们讲解了如何给ubuntu安装远程桌面及其配置,这篇文章我们再来讲解下有关ubuntu远程桌面会话的问题. 一.问题描述 在我们使用ub ...
- Citrix 服务器虚拟化之十八 桌面虚拟化之部署MCS随机桌面池
Citrix 服务器虚拟化之十八 桌面虚拟化之部署MCS随机桌面池 完成桌面模版的制作后,可以开始虚拟桌面池的发布 说明: 环境基于实验十七 1.登录DC服务器创建一个组织单位名为Citrix,然后 ...
- WIN7虚拟桌面创建(多屏幕多桌面)
Windows7/WIN7虚拟桌面怎么用怎么创建多桌面(摘录) 在使用电脑中经常会遇到桌面软件太多了不够用的感慨,那么要是一台电脑有多个桌面就好了.在windows10中自带已经支持了虚拟桌面,在wi ...
- win10 常用设置 桌面出来计算机图标,固定桌面摆好的图标设置方法,电脑设备ID方法
win10 常用设置 桌面出来计算机图标,固定桌面摆好的图标设置方法 桌面右键-->显示设置-->桌面图标设置 电脑设备ID:xxx查看方法:桌面右键-->显示设置-->关于
- mac远程桌面Microsoft Remote Desktop for Mac的安装与使用
mac远程桌面Microsoft Remote Desktop for Mac的安装与使用 学习了:https://blog.csdn.net/ytangdigl/article/details/78 ...
- 【桌面篇】Archlinux安装kde桌面
ArchLinux安装配置手册[桌面篇] 现在你的U盘可以拔掉了,重启后会发现和刚刚没什么区别,还是命令行的界面,别着急现在就带你安装桌面环境. 连接网络 首先检查一下网络是否连接成功 ping ww ...
- WPF将窗口置于桌面下方(可用于动态桌面)
WPF将窗口置于桌面下方(可用于动态桌面) 先来看一下效果: 界面元素很简单,就一个Button按钮,然后写个定时器,定时更新Button按钮中的内容为当前时间,下面来介绍下原理,和界面组成. 窗口介 ...
随机推荐
- 双URL编码绕过WAF
一般编码一次是%5c. 但攻击者怕这个会被认出来,所以用二次编码,把%本身编码成%25.再和后边拼成%255c. 如果URL解码器有缺陷,只不断重复"从前边开始解析"这个步骤,就会 ...
- Cesium Billboard加载Gif图片
https://blog.csdn.net/xietao20/article/details/109404491
- Redis系列21:缓存与数据库的数据一致性讨论
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...
- wget 命令的使用:HTTP文件下载、FTP文件下载--九五小庞
1. wget 命令简介与安装wget是用于在命令行终端下载网络文件的开源免费的命令工具,支持 HTTP/HTTPS.FTP/FTPS 协议的下载.wget 与 curl 相似,curl 可以理解为是 ...
- 想让你的工作轻松高效吗?揭秘Java + React导出Excel/PDF的绝妙技巧!
前言 在B/S架构中,服务端导出是一种高效的方式.它将导出的逻辑放在服务端,前端仅需发起请求即可.通过在服务端完成导出后,前端再下载文件完成整个导出过程.服务端导出具有许多优点,如数据安全.适用于大规 ...
- 我的 Kafka 旅程 - 基于账号密码的 SASL+PLAIN 认证授权 · 配置 · 创建账号 · 用户授权 · .NET接入
本文基于 Kafka 3.0+ 的 KRaft 模式来阐述 默认的 Kafka 不受认证约束,可不用账号就可以连接到服务,也就是默认的 PLAIN 方式,不需要认证:配置了 SASL 认证之后,连接K ...
- Vue源码学习(三):<templete>渲染第二步,创建ast语法树
好家伙,书接上回 在上一篇Vue源码学习(二):<templete>渲染第一步,模板解析中,我们完成了模板解析 现在我们继续,将模板解析的转换为ast语法树 1.前情提要 代码已开 ...
- 为不断增长的Go生态系统扩展gopls
原文在这里. 由 Robert Findley and Alan Donovan 发布于 2023年9月8日 今年夏天初,Go团队发布了gopls的v0.12版本,这是Go语言的语言服务器,它进行了核 ...
- 探析ElasticSearch Kibana在测试工作中的实践应用
一. 为什么使用ES Kibana 离线数据测试中最重要的就是数据验证,一部分需要测试es存储数据的正确性,另一部分就需要验证接口从es取值逻辑的正确性.而为了验证es取值逻辑的正确性,就需要用到Ki ...
- k8s1.25版本上实践 Ingress-nginx
背景: 领导要求的最新最新版本k8s...使用ingress-nginx 对外暴露内部服务 环境 节点 master worker 主机/ip calico-master01/192.168.195. ...