Electron-React18-MacOS桌面管理系統|electron27+react仿mac桌面

来源:https://www.cnblogs.com/xiaoyan2017/archive/2023/11/23/17850653.html
-Advertisement-
Play Games

基於React18+Electron27+ArcoDesign仿macOS桌面端系統框架ElectronMacOS。 electron-react-macOs 基於electron27.x+vite4+react18+arcoDesign+zustand等技術構建桌面版仿MacOs框架系統解決方案。 ...


基於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桌面多層級路由菜單
 * 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桌面系統的一些分享,希望對大家有些幫助哈~~

最後附上兩個最近實例項目

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

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

 

本文為博主原創文章,未經博主允許不得轉載,歡迎大家一起交流 QQ(282310962) wx(xy190310)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 什麼是資料庫 資料庫是結構化信息或數據(一般以電子形式存儲在電腦系統中)的有組織的集合,通常由資料庫管理系統 (DBMS) 來控制。在現實中,數據、DBMS 及關聯應用一起被稱為資料庫系統,通常簡稱為資料庫。 為了提高數據處理和查詢效率,當今最常見的資料庫通常以行和列的形式將數據存儲在一系列 ...
  • MySQL介紹 相關概念 DB(DataBase)資料庫。是一個存儲數據的容器 DBA(Database Administrator):資料庫管理員。負責資料庫的管理和維護的專業人員 DBMS(Database Management System):資料庫管理系統。是一種軟體,用於創建和管理資料庫。 ...
  • sql(Structured Query Language: 結構化查詢語言)是高級的費過程化編程語言,允許用戶在高層數據結構上工作, 是一種數據查詢和程式設計語言, 也是(ANSI)的一項標準的電腦語言. but... 目前仍然存在著許多不同版本的sql語言,為了與ANSI標準相相容, 它們必須... ...
  • 環境說明:阿裡雲ECS,2核2G(新老用戶同享,僅需99/年),X86架構,CentOS 7.9操作系統。 準備工作 1.查看當前伺服器是否安裝了mariadb,如果有安裝需要先刪除,如下圖所示。 # 檢查當前伺服器是否安裝mariadb rpm -qa |grep mariadb # 卸載已安裝的 ...
  • 近日,CCF TF 舉辦了第 123 期分享活動,本期主題為“用戶體驗工程”。在活動中,來自火山引擎 AB 測試平臺的專家結合位元組跳動的 AB 實驗經驗,進行了《數據驅動的實驗文化》為主題的現場分享。 ...
  • 相信大家都認可一個觀點:不論是 To B 還是 To C,用戶是企業的核心資源,是互聯網產品中最重要的價值之一。因此,深入挖掘用戶價值成為現在大部分企業運營的關鍵。 之前我們為大家介紹過如何利用 RFM 模型讓企業聚焦於更有價值的用戶,本文將為大家詳細介紹用戶生命周期模型 APMDR,以及「袋鼠雲客 ...
  • FE層的架構都能在網上找到說明. 但BE層的架構模式、一致性保障、與FE層之間的請求邏輯,數據傳輸邏輯等,我個人暫時沒有找到相應的博客說明這些的。當然這些是我個人在學習與使用Doris過程中,對內部交互邏輯與實現感興趣才有這些疑問. 還好現在有GPT這類大模型,有了疑問,只要問題描述得當,大多可以解 ...
  • 簡介: 問題: C#,VS2022,mariadb-10.11.5-winx64,using MySql.Data.MySqlClient; 在執行connection.Open()時拋出異常:System.InvalidCastException:“Object cannot be cast fr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...