最近一直在摸索Electron,網上有大牛使用Electron-vue作的一些應用,但因爲本人非科班開發人員,學習起來老是雲裏霧裏的,最終仍是迴歸原始的Electron開發,再逐步拓展其餘棧的知識。好了,接下來是最近臨摹的一個Electron記事本,原文是簡書的做者鰻駝螺寫的一個教程————從零開始寫一個記事本app,地址 https://www.jianshu.com/p/57d910008612/css
因爲做者寫的時候是2017年,版本已經很是久遠了,第一次寫的時候愣是沒跑起來,並且功能相對簡單,想要做爲平時使用仍是有點欠缺的,所以本人對該記事本App作了一些優化,增長了文件拖放讀取,字數統計和另存爲功能,用起來也仍是能夠的,目前禁用了最大化和自由縮放窗體的功能,由於一些適配效果不理想,所以暫時放棄該功能,先來看下效果吧。html
用到的知識點比較多,主要是vue
const {app, BrowserWindow, ipcMain, Menu} = require('electron'); const path = require('path'); // 主菜單模板 const menuTemplate = [ { label: ' 文件 ', submenu: [ { label: '新建', accelerator: 'CmdOrCtrl+N', click: function() { mainWindow.webContents.send('action', 'new') } }, { label: '打開', accelerator: 'CmdOrCtrl+O', click: function() { mainWindow.webContents.send('action', 'open') } }, { label: '保存', accelerator: 'CmdOrCtrl+S', click: function() { mainWindow.webContents.send('action', 'save') } }, { label: '另存爲... ', accelerator: 'CmdOrCtrl+Shift+S', click: function() { mainWindow.webContents.send('action', 'save-as') } }, { type: 'separator' }, { label: '退出', click: function() { mainWindow.webContents.send('action', 'exit') } } ] }, { label: ' 編輯 ', submenu: [ { label: '返回', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重作', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, //分隔線 { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '複製', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘貼', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '刪除', accelerator: 'CmdOrCtrl+D', role: 'delete' }, { type: 'separator' }, //分隔線 { label: '全選', accelerator: 'CmdOrCtrl+A', role: 'selectall' } ] }, { label: ' 幫助 ', submenu: [ { label: '關於... ', click: async () => { const { shell } = require('electron'); await shell.openExternal('https://segmentfault.com/u/shaomeng'); } } ] } ]; // 主窗體 let mainWindow; // 安全退出初始化 let safeExit = false; // 構建主菜單 let menu = Menu.buildFromTemplate (menuTemplate); Menu.setApplicationMenu (menu); // 主窗體初始化 function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 620, resizable:false, backgroundColor: '#e9e9e9', webPreferences: { nodeIntegration: true, preload: path.join(__dirname, 'preload.js') } }); // 加載頁面內容 mainWindow.loadFile('index.html'); // 開發者工具 // mainWindow.webContents.openDevTools(); // 窗體生命週期 close 操做 mainWindow.on('close', (e) => { if(!safeExit) { e.preventDefault(); } mainWindow.webContents.send('action', 'exit'); }); // 窗體生命週期 closed 操做 mainWindow.on('closed', function() { mainWindow = null; }); } // 程序生命週期 ready app.on('ready', createWindow); // 程序生命週期 window-all-closed app.on('window-all-closed', function() { if (process.platform !== 'darwin') app.quit(); }); // 程序生命週期 activate app.on('activate', function() { if (mainWindow === null) createWindow(); }); // 接收退出命令 ipcMain.on('exit', function() { safeExit = true; app.quit(); });
const ipcRenderer = require('electron').ipcRenderer; // electron 通訊模塊 const remote = require('electron').remote; // electron 主進程與渲染進程通訊模塊 const Menu = remote.Menu; // electron renderer進程的菜單模塊 const dialog = remote.dialog; // electron 對話框模塊 // 初始化基本參數 let isSave = true; // 初始狀態無需保存 let txtEditor = document.getElementById('txtEditor'); // 獲取文本框對象 let currentFile = null; // 初始狀態無文件路徑 let isQuit = true; // 初始狀態可正常退出 // 右鍵菜單模板 const contextMenuTemplate = [ { label: '返回', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重作', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, //分隔線 { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '複製', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘貼', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '刪除', accelerator: 'CmdOrCtrl+D', role: 'delete' }, { type: 'separator' }, //分隔線 { label: '全選', accelerator: 'CmdOrCtrl+A', role: 'selectall' } ]; // 構建右鍵菜單 const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); txtEditor.addEventListener('contextmenu', (e) => { e.preventDefault(); contextMenu.popup(remote.getCurrentWindow()); }); // 檢測編輯器是否有內容更新,統計字數 txtEditor.oninput = (e) => { if (isSave) { document.title += ' *'; } isSave = false; // 字數統計 wordsCount(); } // 菜單操做 ipcRenderer.on('action', (event, arg) => { switch(arg) { case 'new': // 新建文檔 askSaveNeed(); initDoc(); break; case 'open': // 打開文檔 askSaveNeed(); openFile(); wordsCount(); break; case 'save': // 保存當前文檔 saveCurrentDoc(); break; case 'save-as': // 另存爲當前文檔 currentFile = null; saveCurrentDoc(); break; case 'exit': // 退出 askSaveNeed(); if(isQuit) { // 正常退出 ipcRenderer.sendSync('exit'); } isQuit = true; // 復位正常退出 break; } }); // 初始化文檔 function initDoc() { currentFile = null; txtEditor.value = ''; document.title = 'Notepad - Untitled'; isSave = true; document.getElementById("txtNum").innerHTML = 0; } // 詢問是否保存命令 function askSaveNeed() { // 檢測是否須要執行保存命令 if (isSave) { return; } // 彈窗類型爲 message const options = { type: 'question', message: '請問是否保存當前文檔?', buttons: [ 'Yes', 'No', 'Cancel'] } // 處理彈窗操做結果 const selection = dialog.showMessageBoxSync(remote.getCurrentWindow(), options); // 按鈕 yes no cansel 分別爲 [0, 1, 2] if (selection == 0) { saveCurrentDoc(); } else if(selection == 1) { console.log('Cancel and Quit!'); } else { // 點擊 cancel 或者關閉彈窗則禁止退出操做 console.log('Cancel and Hold On!'); isQuit = false; // 阻止執行退出 } } // 保存文檔,判斷新文檔or舊文檔 function saveCurrentDoc() { // 新文檔則執行彈窗保存操做 if(!currentFile) { const options = { title: 'Save', filters: [ { name: 'Text Files', extensions: ['txt', 'js', 'html', 'md'] }, { name: 'All Files', extensions: ['*'] } ] } const paths = dialog.showSaveDialogSync(remote.getCurrentWindow(), options); if(paths) { currentFile = paths; } } // 舊文檔直接執行保存操做 if(currentFile) { const txtSave = txtEditor.value; saveText(currentFile, txtSave); isSave = true; document.title = "Notepad - " + currentFile; } } // 選擇文檔路徑 function openFile() { // 彈窗類型爲openFile const options = { filters: [ { name: 'Text Files', extensions: ['txt', 'js', 'html', 'md'] }, { name: 'All Files', extensions: ['*'] } ], properties: ['openFile'] } // 處理彈窗結果 const file = dialog.showOpenDialogSync(remote.getCurrentWindow(), options); if(file) { currentFile = file[0]; const txtRead = readText(currentFile); txtEditor.value = txtRead; document.title = 'Notepad - ' + currentFile; isSave = true; } } // 執行保存的方法 function saveText( file, text ) { const fs = require('fs'); fs.writeFileSync( file, text ); } // 讀取文檔方法 function readText(file) { const fs = require('fs'); return fs.readFileSync(file, 'utf8'); } // 字數統計 function wordsCount() { var str = txtEditor.value; sLen = 0; try{ //先將回車換行符作特殊處理 str = str.replace(/(\r\n+|\s+| +)/g,"龘"); //處理英文字符數字,連續字母、數字、英文符號視爲一個單詞 str = str.replace(/[\x00-\xff]/g,"m"); //合併字符m,連續字母、數字、英文符號視爲一個單詞 str = str.replace(/m+/g,"*"); //去掉回車換行符 str = str.replace(/龘+/g,""); //返回字數 sLen = str.length; }catch(e){ console.log(e); } // 刷新當前字數統計值到頁面中 document.getElementById("txtNum").innerHTML = sLen; } // 拖拽讀取文檔 const dragContent = document.querySelector('#txtEditor'); // 阻止 electron 默認事件 dragContent.ondragenter = dragContent.ondragover = dragContent.ondragleave = function() { return false; } // 拖拽事件執行 dragContent.ondrop = function(e) { e.preventDefault(); // 阻止默認事件 currentFile = e.dataTransfer.files[0].path; // 獲取文檔路徑 const txtRead = readText(currentFile); txtEditor.value = txtRead; document.title = 'Notepad - ' + currentFile; isSave = true; wordsCount(); }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Notepad</title> <style type="text/css"> body,html{ margin:0; padding:0; } #txtEditor{ width:786px; height:539px; padding:4px; margin:0px; border:0px; font-size: 16px; resize:none; outline:none; } #txtEditor:focus{ border:0px; outline:none; } .bottom { height: 20px; font-size: 12px; color: #666666; text-align: right; padding-right: 20px; display: block; } </style> </head> <body> <textarea id="txtEditor"></textarea> <div class="bottom">字數:<span id="txtNum">0</span></div> <script src="./renderer.js"></script> </body> </html>
代碼量對於新手來講已經很是多了,但實際上使用的都是很是基礎並且容易閱讀的格式,並且我都一一作了註釋,建議像我這樣的新手,能夠採用逐個功能擊破的方式,一點點了解代碼原理,好比逐個完成主菜單中的項目,每一個功能模塊都逐一調通,有問題能夠留言,我都會盡量回復,雖然我仍是個初學者^_^。html5