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



    
    e-text
    
    


    
    


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 从入门到实践之实战-记事本的开发_第1张图片

6、打包

查看是否可以使用electron-packager命令

electron-packager --version

有正常返回表示可以使用该命令
否则,安装electron-packager

cnpm install -g electron-packager

运行打包命令

electron-packager .

运行后会生成一个文件夹,里面会包含一个可执行文件,本例使用的是windows,因此会生成一个.exe的可执行文件

你可能感兴趣的:(Electron,electron,桌面端开发,记事本)