天天看點

Electron 從入門到實踐之實戰-記事本的開發

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
           

如下圖表示運作成功

Electron 從入門到實踐之實戰-記事本的開發

6、打包

檢視是否可以使用electron-packager指令

electron-packager --version
           

有正常傳回表示可以使用該指令

否則,安裝electron-packager

cnpm install -g electron-packager
           

運作打包指令

electron-packager .
           

運作後會生成一個檔案夾,裡面會包含一個可執行檔案,本例使用的是windows,是以會生成一個.exe的可執行檔案

繼續閱讀