Electron 從入門到實踐之實戰-記事本的開發
1、初始化項目
建立一個目錄用于存放第一個e_text
mkdir e_text #注意 windows沒有mkdir指令,直接建立檔案夾即可
進入目錄後,初始化
npm init # 這裡要根據提示輸入相關内容
建立好必要檔案
touch index.html
touch main.js
touch event.js #注意 windows沒有touch指令,直接建立檔案即可
修改packge.json
{
"name": "e-text",
"productName": "electron-text",
"author": "admin",
"version": "1.0.0",
"description": "e-text",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"license": "ISC",
"devDependencies": {
"electron": "^5.0.4"
},
"dependencies": {
"electron-packager": "^14.0.0",
"electron-updater": "^4.0.6",
"jquery": "^3.4.1"
}
}
安裝依賴
由于是在國内,建議使用cnpm
cnpm intall
2、編寫入口檔案main.js
const { app, BrowserWindow, Menu, Tray, ipcMain } = require('electron') // 引入electron
// 保持對window對象的全局引用,如果不這麼做的話,當JavaScript對象被
// 垃圾回收的時候,window對象将會自動的關閉
let win
// 托盤應用需要的
let tray;
let contextMenu
function createWindow () {
win = new BrowserWindow({
// 設定視窗大小
width: 800,
height: 500,
webPreferences: {
nodeIntegration: true
}
})
console.log('system edition:', process.platform) // darwin:表示macos;linux:表示linux;win32:表示Windows;
// 使用模版建立菜單
const template = [
{
label: '檔案',
submenu: [
{
label: '建立檔案',
click:()=>{
win.webContents.send('action', 'newfile');
}
},
{
type: 'separator'
},
{
label: '打開檔案',
click:()=>{
win.webContents.send('action', 'openfile');
}
},
{
type: 'separator'
},
{
label: '儲存檔案',
click:()=>{
win.webContents.send('action', 'savefile');
}
},
{
type: 'separator'
},
{
label: '關閉',
accelerator: 'Ctrl+Q', // 設定菜單快捷鍵
click: ()=>{win.close()}
}
]
},
{
label: '編輯',
submenu: [
{
label: '複制',
role:'copy',
click:()=>{win.webContents.copy()} // 在點選時執行複制指令
},
{
label: '粘貼',
role:'paste',
click:()=>{win.webContents.paste()} // 在點選時執行粘貼指令
},
{
label: '剪切',
role:'cut',
click:()=>{win.webContents.cut()}
},
{
type:'separator' // 設定菜單項分隔條
},
{
label: '撤銷',
role:'undo',
click:()=>{win.webContents.undo()} // 在點選時執行撤銷指令
},
{
label: '重做',
role:'redo',
click:()=>{win.webContents.redo()} // 在點選時執行重做指令
}
]
}
];
const menu = Menu.buildFromTemplate(template);
// 開始設定菜單
Menu.setApplicationMenu(menu);
// 加載index.html檔案
win.loadFile('index.html')
// 打開調試工具
// win.webContents.openDevTools()
// 監聽視窗關閉的事件,監聽到時将一個消息發送給渲染程序
win.on('close', (e) => {
e.preventDefault();
// 給渲染程序發消息
win.webContents.send('action', 'exiting');
});
// 當 window 被關閉,這個事件會被觸發。
win.on('closed', () => {
// 取消引用 window 對象,如果你的應用支援多視窗的話,
// 通常會把多個 window 對象存放在一個數組裡面,
// 與此同時,你應該删除相應的元素。
win = null
})
}
// Electron 會在初始化後并準備
// 建立浏覽器視窗時,調用這個函數。
// 部分 API 在 ready 事件觸發後才能使用。
app.on('ready', createWindow)
// 當全部視窗關閉時退出。
app.on('window-all-closed', () => {
// 在 macOS 上,除非使用者用 Cmd + Q 确定地退出,
// 否則絕大部分應用及其菜單欄會保持激活。
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上,當單擊dock圖示并且沒有其他視窗打開時,
// 通常在應用程式中重新建立一個視窗。
if (win === null) {
createWindow()
}
})
// 監聽與渲染程序的通訊,監聽來自渲染程序的消息,當監聽到确定關閉時,将所有視窗退出
ipcMain.on('reqaction', (event, arg) => {
console.log('zhu jin cheng:', arg)
switch(arg){
case 'exit':
app.exit() // 退出所有視窗,注意這裡使用 app.quit() 無效
break;
}
});
3、編寫index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>e-text</title>
<link rel="stylesheet" href="static/css/font-awesome.min.css" target="_blank" rel="external nofollow" >
<style type="text/css">
body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
overflow-y: hidden;
}
#newText {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
outline: none;
border-width: 0;
resize: none;
font-size: 18px;
}
#willshow:hover {
cursor: pointer;
}
</style>
</head>
<body>
<textarea id="newText"></textarea>
<script src="event.js"></script>
</body>
</html>
4、編寫event.js
const remote = require('electron').remote;
const ipcRenderer = require('electron').ipcRenderer;
const dialog = remote.dialog;
const Menu = remote.Menu;
const fs = require('fs');
var $ = require('jquery');
var os = require('os');
let platform = os.platform();
let filePath = ''
let fileDocument = document.getElementById('newText')
let isSave = true
// 用來解釋主程序和渲染程序的執行個體
// 流程:先在主程序中監聽視窗的close事件,然後當發生點選時,将消息從主程序發送到渲染程序。渲染程序收到消息後執行某些操作後,将消息發回主程序,由主程序執行剩下的操作
function eventQuit() {
var options = {};
// options.title = '确定退出嗎?';
options.message = '确定退出嗎?';
options.type = 'none';
options.buttons = ['Yes', 'No'];
dialog.showMessageBox(options, (response) => {
console.log('目前被單擊的按鈕索引是' + response);
if (response == 0) {
ipcRenderer.send('reqaction', 'exit');
}
})
}
function setNew() {
document.getElementById('newText').value = ''
filePath = ''
window.document.title = '無标題文檔'
}
function askChoice(ask_type) {
if(ask_type == 0) {
setNew()
} else if (ask_type == 1) {
filePath = openFile()
if (filePath) {
readFile(filePath)
document.title = returnFileName(filePath)
}
} else {
ipcRenderer.send('reqaction', 'exit');
}
}
// 詢問是否儲存目前文檔 ask_type: 0 newfile 1 openfile 2 exit
function askSave(ask_type) {
if (isSave == false) {
var options = {};
options.title = '是否将目前文檔儲存?';
options.message = '是否将目前文檔儲存?';
options.type = 'none';
options.buttons = ['Yes', 'No'];
dialog.showMessageBox(options, (response) => {
if (response == 0) {
if (filePath == '') {
// 沒有被儲存過的新文檔,需要先打開儲存對話框
filePath = openSaveDialog()
writeFile(filePath, fileDocument.value)
askChoice(ask_type)
} else {
// 已經被儲存過的,存在路徑,直接儲存檔案
writeFile(filePath, fileDocument.value)
askChoice(ask_type)
}
} else{
askChoice(ask_type)
}
})
}
}
// 擷取檔案名
function returnFileName(filePath) {
if (platform == 'linux' || platform == 'darwin') {
var fileList = filePath.split('/')
} else {
var fileList = filePath.split('\\')
}
return fileList[fileList.length - 1]
}
// 寫入文檔
function writeFile(filePath, fileData) {
fs.writeFileSync(filePath, fileData);
isSave = true
}
// 讀取文檔
function readFile(filePath) {
window.document.getElementById('newText').value = fs.readFileSync(filePath, 'utf8');
}
// 打開儲存對話框并傳回路徑
function openSaveDialog() {
var options = {};
options.title = '儲存檔案';
options.buttonLabel = '儲存';
options.defaultPath = '.';
options.nameFieldLabel = '儲存檔案';
options.showsTagField = false;
options.filters = [
{name: '文本檔案', extensions: ['txt','js','html','md']},
{name: '所有檔案', extensions: ['*']}
]
// 儲存成功傳回一個路徑,否則傳回 undefined
var path = dialog.showSaveDialog(options)
return path == undefined ? false : path
}
// 打開打開對話框并傳回路徑
function openFile(){
var options = {};
options.title = '打開檔案';
options.buttonLabel = '打開';
options.message = '打開檔案';
options.defaultPath = '.';
options.properties = ['openFile'];
options.filters = [
{name: '文本檔案', extensions: ['txt','js','html','md']}
]
// 打開成功傳回一個數組,第一個元素為打開的路徑,否則傳回 undefined
var path = dialog.showOpenDialog(options)
return path == undefined ? false : path[0]
}
//監聽與主程序的通信
ipcRenderer.on('action', (event, arg) => {
switch (arg) {
case 'exiting':
if (fileDocument.value == '' || fileDocument.value == null || isSave == true) {
eventQuit()
} else {
askSave(2)
}
break;
case 'newfile':
if (fileDocument.value == '' || fileDocument.value == null || isSave == true) {
setNew()
} else {
askSave(0)
}
break;
case 'openfile':
if (fileDocument.value == '' || fileDocument.value == null || isSave == true) {
filePath = openFile()
if (filePath) {
readFile(filePath)
document.title = returnFileName(filePath)
}
} else {
askSave(1)
}
break;
case 'savefile':
if (!filePath) filePath = openSaveDialog()
if (filePath) {
writeFile(filePath, fileDocument.value)
document.title = returnFileName(filePath)
}
break;
}
});
// 當打開頁面時就會執行 onload。當使用者進入後及離開頁面時,會觸發 onload 和 onunload 事件。
window.onload = function() {
console.log('開始',platform)
let newText = document.getElementById('newText')
const contextMenuTemplate = [
{ label: '複制', role: 'copy' },
{ label: '剪切', role: 'cut' },
{ label: '粘貼', role: 'paste' },
{ label: '删除', role: 'delete' }
];
const contextMenu = Menu.buildFromTemplate(contextMenuTemplate);
newText.addEventListener('contextmenu',function(event) {
event.preventDefault();
contextMenu.popup(remote.getCurrentWindow());
})
document.getElementById('newText').onkeyup = function(event) {
switch (event.keyCode) {
case 9:
newText.value += ' '
break;
}
}
document.getElementById('newText').oninput = function (event) {
if (isSave) document.title += " *"
isSave = false
}
}
// 監聽浏覽器視窗變化時執行的函數
window.onresize = function(){
document.getElementsByTagName('body')[0].style.height = window.innerHeight+'px';
document.getElementById('newText').focus();
}
5、本地運作
electron .
或
npm start
如下圖表示運作成功
6、打包
檢視是否可以使用electron-packager指令
electron-packager --version
有正常傳回表示可以使用該指令
否則,安裝electron-packager
cnpm install -g electron-packager
運作打包指令
electron-packager .
運作後會生成一個檔案夾,裡面會包含一個可執行檔案,本例使用的是windows,是以會生成一個.exe的可執行檔案