Electron桌面应用&&实现一个简单MarkDown应用

Electron介绍

Electron集成了chromium(支持最新特性的浏览器)与Node.js(javascript运行时,可实现文件读写)Native API是(提供统一原生界面操作的能力)
工作流程:启动APP 启动主进程,创建窗口,加载指定界面,开启渲染进程,渲染进程需要进行通信(如新增歌曲打开文件夹选择MP3文件)需通过主进程调用原生API进行操作
主进程可以看做是package.json中的main属性所对应的文件,一个应用只有一个主进程,只有主进程可以进行GUI操作,windows中展示的界面通过渲染进程实现,一个应用有多个渲染进程。
官网中含有起步项目
electron实质是在node环境下使用electron包

生命周期

ready: app 初始化完成
dom-ready: 一个窗口中的文本加载完成
did-finsh-load: 导航完成时触发窗口
window-all-closed: 所有窗口都被关闭时触发
before-quit: 在关闭窗口之前触发
will-quit: 在窗口关闭并且应用退出时触发
quit: 当所有窗口被关闭时触发
closed: 当窗口关闭时触发,此时应删除窗口引用
自启动配置
image
可以将窗体默认不去显示,然后监听窗体的ready-to-show事件让窗体进行显示
渲染进程是html通过<script>标签导入的js,在渲染进程引入const o = require('electron')去创建窗口(须在创建主进程窗口时添加如下红框中的配置),ctrl+shift+i调出调试的面板
image
通过remote模块与主进程进行通信
自定义窗口样式可将frame设置为false隐藏,然后在html文件中通过css来设置,js的编写下载DOMContentLoaded的监听中,通过原生方法获取元素
window.onbeforeunload 是一个JavaScript 事件处理器,在用户关闭当前页面(标签页)之前触发。
模态窗口,子窗口没有关闭之前父窗口不允许使用
image

自定义菜单

主进程模块下的Menu模块
角色和类型,配置项的role字段和type字段,设置快捷键
动态创建菜单
image
自定义右键菜单:1)用Menu创建一个自定义菜单内容2)在鼠标右击行为发生之后显示出来
image

主进程与渲染进程之间通信

ipcMain模块是在主进程通信需要用到的
image
渲染进程的消息是在浏览器中查看到的,同步APIsend加上Sync
image
主进程给渲染进程通信,需要现有一个menu,主进程获取当前窗口使用APIgetFocusedWindow()
image
渲染进程和渲染进程之间通信
一种方式是通过主进程,另一种方式是要通过localStorage
通过id获取到窗口image
dialog模块、shell模块和iframe模块
消息通知
image
全局快捷键globalShortcut模块
剪切板操作(clipboard字剪切,nativeImage图片剪切模块)

react和electron的markdowm项目

首先初始化一个react项目,然后在package.json中添加mian字段,为main.js,在main.js中引入electron来创建应用
使用以下工具创建应用
bootstrap: UI组件库
styled-components:自定义样式(一个流行的 CSS-in-JS 库)
将react启动窗口放置到electron生成的APP窗口中

  const urlLocation = isDev ? "http://localhost:3000" : 'myUrl'
  mainWindow.loadURL(urlLocation)

利用工具创建命名启动时不启动浏览器直接启动elecrtron应用,并且等到本地localhost完全运行后在显示桌面应用不至于有太长的白屏时间

"dev": "concurrently \"cross-env BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\""
electron-is-dev 
concurrently: 连接多个命令,中间使用空格分开
wait-on:等待某个结果执行之后再去执行后续的命令
cross-env : 跨平台的环境变量设置 

软件图标库
https://fontawesome.com/icons
@fortawesome/fontawesome-svg-core: 核心文件需要安装
@fortawesome/react-fontawesome :react 风格
@fortawesome/free-solid-svg-icons :solid 类型字体库
react中判断弄tab页时判断是否要添加该类名需要用classNames的包
markdown开源的能够和react开发的编辑器react-simplemde-editor,可在github上查看它的使用

利用文件方法来进行真正的文件读取,重命名,删除,保存操作

封装文件读取,删除重命名方法在js中,通过electron-store进行存储

执行导入模块

const importFile = () =>{
	remote.dialog.showOpenDialog({
	defaultPath:__dirname,
	buttonLabe:'请选择',
	title:"选择md文件",
	properties:['openFile','multiSelections'],
	filters:[
	{"name":"md文档",extensions:["md"]},
	{"name":"其他类型",extensions:["js","json","html"]}
	]
	}).then((ret)=>{
		const paths = ret.filePaths
		if(paths.length){
		 // 01 判断当前路径们,是否存在于files当中,如果已经存在则无需在执行导入操作const validPaths
		 
		 //02 将上述的路径信息组装成files格式,id title path
		 //03 将上述数据格式处理为files所需要的
		 // 04 更新数据重新渲染
		 // 完成数据持久化操作
		 //成功导入提示
		 if(packageData.length){
		 	remote.dialog.showMessageBox({
			 type:'info',
			 title:'导入md文档',
			 message:'文件导入成功'
			})
		 }
		}else{
			console.log('未选择文件导入!')
		}
	})
}

自定义钩子实现右键菜单

将所需要的数据通过data-自定义菜单来挂载到元素上例如<li data-id={file.id}></li>
因为自定义属性挂载在li上,如果点击li中的元素就获取不到自定义属性值,所以需要获取到它的父级元素上面带着data-自定义属性的方法

export const getParentNode = (node,parentClassName)=>{
	let currentEle = node 
	while(currentEle !== null) {
	 if(currentEle.classList.contains(parentClassName)){
	 	return currentEle
	 }
	 currentEle =currentEle.parentNode
	}
}

传入右键菜单配置项和类名(只在这触发显示右键菜单的元素)来生成右键菜单

function useContextMenu(contextMenuTmp, areaClass) {
  const currentEle = useRef(null)
  useEffect(() => {
    // 获取需要触发右键菜单的区域的元素
    const areaEle = document.querySelector(areaClass)
    const menu = Menu.buildFromTemplate(contextMenuTmp)
    const contextMenuHandle = (ev) => {
      if (areaEle.contains(ev.target)) {
        currentEle.current = ev.target
        menu.popup({ window: remote.getCurrentWindow })
      }
    }
    window.addEventListener('contextmenu', contextMenuHandle)
    return () => {
      window.removeEventListener('contextmenu', contextMenuHandle)
    }
  })
  return currentEle
}

自定义原生菜单

自定义原生菜单用到原生菜单主进程和渲染进程之间的通信

const template = [
  {
    label: '文件',
    submenu: [
      {
        label: '新建',
        accelerator: 'CmdOrCtrl+N',
        click(menuItem, browserWindow, event) {
          browserWindow.webContents.send('execute-create-file')
        }
      },
    ]
  },
  {
    label: '编辑',
    submenu: [
      {
        label: '撤销',
        accelerator: 'CmdOrCtrl + Z',
        role: 'undo'//已有的功能
      },
      {
        type: 'separator'
      },
    ]
  },
  {
    label: '视图',
    submenu: [
      {
        label: '刷新',
        accelerator: 'Shift + CmdOrCtrl + R',
        click(item, focusedWindow) {
          if (focusedWindow) {
            focusedWindow.reload()
          }
        }
      },
    ]
  },
  {
    label: '帮助',
    role: 'help',
    submenu: [
      {
        label: '更多',
        click() {
          shell.openExternal("http://electronjs.org")
        }
      }
    ]
  }
]

在渲染进程中通信定义自定义钩子

import { useEffect } from 'react'
const { ipcRenderer } = window.require('electron')

function useIpcRenderer(actionMap) {
  useEffect(() => {
    Object.keys(actionMap).forEach((action) => {
      ipcRenderer.on(action, actionMap[action])
    })
    return () => {
      Object.keys(actionMap).forEach((action) => {
        ipcRenderer.removeListener(action, actionMap[action])
      })
    }
  })
}

其使用

 // 实现主进程与渲染进程的事件通信
  useIpcRenderer({
    'execute-create-file': createFile,
  })
posted @ 2024-05-10 15:55  穹顶之下影  阅读(34)  评论(0编辑  收藏  举报