Electron構建一個文件瀏覽器應用(一)


在window、mac、linux系統中,他們都有一個共同之處就是以文件夾的形式來組織文件的。並且都有各自的組織方式,以及都有如何查詢和顯示哪些文件給用戶的方法。那么從現在開始我們來學習下如何使用Electron來構建文件瀏覽器這么一個應用。

注意:我也是通過看書,看資料來學習的。這不重要,重要的是我們學到東西。我們知道如何使用 electron 來做一個桌面型應用軟件。有這些知識點后,以后我們做其他的桌面型應用軟件會有基礎。

那么既然是文件瀏覽器,那么我們可以給文件瀏覽器取一個名字,假如叫他為 FileBrowser. 那么該文件瀏覽器要具備如下功能:

1. 用戶可以瀏覽文件夾和查找文件。
2. 用戶可以使用默認的應用程序打開文件。

Electron應用它是以一個js文件作為入口文件的,所以呢我們需要和之前一篇文章講的一樣,我們需要看下有如下目錄結構:

|------- FileBrowser
|  |--- main.js
|  |--- index.html
|  |--- package.json

package.json 目前的代碼如下:

{
  "name": "electron-filebrowser",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

main.js 基本代碼如下(和第一篇文章的實現hello world代碼是一樣的):

'use strict';

// 引入 全局模塊的 electron模塊
const electron = require('electron');

// 創建 electron應用對象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定義變量 對應用視窗的引用 
let mainWindow = null;

// 監聽視窗關閉的事件(在Mac OS 系統下是不會觸發該事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   創建一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

然后我們的index.html 代碼如下:

<html>
  <head>
    <title>FileBrowser</title>
  </head>
  <body>
    <h1>welcome to FileBrowser</h1>
  </body>
</html>

然后我們在項目的根目錄下 運行 electron . 命運,即可打開應用窗口,如下所示:

現在我們需要實現如下這個樣子的;如下所示:

1. 通過Node.js找到用戶個人文件夾所在的路徑

想要顯示用戶個人文件夾的路徑,我們先得想辦法獲取到該路徑,並且要支持window、mac、及linux系統。在mac系統中,用戶個人文件夾在 /User/<username> , 這里的username是用戶名(我這邊是 /User/tugenhua), 在linux系統中,用戶的個人文件夾位於 /home/<username>. 在window10中,則位於C盤的 /User/<username>. 因此不同的操作系統它處於的位置不同。

在Node.js 中有一個叫 osenv 模塊即可解決如上不同位置的問題,有個函數 osenv.home()可以返回用戶個人文件夾。
要使用該模塊,我們可以先進行安裝,當然我們也要安裝fs模塊,需要對文件操作,因此如下命令安裝:

npm install osenv fs --save

因此我們現在需要在 main.js 中加上該模塊的代碼,最終main.js 變成如下代碼:

'use strict';

// 引入 全局模塊的 electron模塊
const electron = require('electron');

// 在應用中加載node模塊
const fs = require('fs');
const osenv = require('osenv');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 來獲取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}
/*
 該函數的作用是:獲取到用戶個人文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();
  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
  });
}

main();

// 創建 electron應用對象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定義變量 對應用視窗的引用 
let mainWindow = null;

// 監聽視窗關閉的事件(在Mac OS 系統下是不會觸發該事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   創建一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

然后我們繼續在命令行中運行:electron . ,然后我們會看到如下效果:

現在我們已經知道了如何獲取用戶個人文件夾下的文件列表了。現在我們要考慮的問題是:如何獲取文件名及文件類型(是文件還是文件夾)。並將他們以不同的圖標在界面上顯示出來。

如上代碼我們已經獲取到了文件列表,現在我們可以以文件列表作為參數,將它傳遞給Node.js文件系統的API中的另一個函數,
該函數要做的事情是:能夠識別是文件還是文件夾以及他們的名字和完整的路徑。要完成這些事情,我們要做如下三件事:

1. 使用 fs.stat函數。來讀取文件狀態。
2. 使用 async模塊來處理調用一系列異步函數的情況並收集他們的結果。
3. 將結果列表傳遞給另一個函數將他們顯示出來。

因此我們首先要安裝 async 模塊,安裝命令如下:

npm install async --save

因此我們的main.js 繼續添加代碼,代碼變成如下:

'use strict';

// 引入 全局模塊的 electron模塊
const electron = require('electron');

// 在應用中加載node模塊
const fs = require('fs');
const osenv = require('osenv');

// 引入 aysnc模塊
const async = require('async');
// 引入path模塊
const path = require('path');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 來獲取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判斷是否是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判斷是否是目錄
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模塊調用異步函數並收集結果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

// 該函數的作用是顯示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach((file) => {
    console.log(file);
  });
}


/*
 該函數的作用是:獲取到用戶個人文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();
  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    /*
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
    */
    inspectAndDescribeFiles(folderPath, files, displayFiles);
  });
}

main();

// 創建 electron應用對象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定義變量 對應用視窗的引用 
let mainWindow = null;

// 監聽視窗關閉的事件(在Mac OS 系統下是不會觸發該事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   創建一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

保存完該 main.js 后,我們接着運行 electron . 命令即可在命令行窗口打印出 對象 {file: '', path: '', type: '' }這樣的了,如下所示:

2. 視覺上顯示文件和文件夾

在如上main.js 代碼中,我們在該文件中有個函數 displayFiles ,我們可以繼續在該函數內部處理將文件名以及對應的圖標展示在界面上。由於要顯示的文件比較多,因此我們會將每個文件定義一套模板,然后為每個文件創建一個該模板的實列再渲染到界面上。

首先我們在index.html文件中添加html模板,模板中包含一個div元素,在其中包含了要顯示的文件信息,因此index.html代碼變成如下:

<html>
  <head>
    <title>FileBrowser</title>
    <link rel="stylesheet" href="./app.css" />
  </head>
  <body>
    <template id="item-template">
      <div class="item">
        <img class='icon' />
        <div class="filename"></div>
      </div>
    </template>
    <div id="toolbar">
      <div id="current-folder">
      </div>
    </div>
    <!-- 該div元素是用來放置要顯示的文件列表信息-->
    <div id="main-area"></div>
    <script src="./app.js" type="text/javascript"></script>
  </body>
</html>

如上代碼 template模板元素的作用是:為每一個渲染的文件信息定義一套HTML模板,真正被渲染到 id為 main-area 元素上,它會將用戶個人文件夾中的每個文件信息都顯示出來。因此下面我們需要在我們的main.js中添加一些代碼,用來創建模板實列並添加到界面上。為了把main.js 啟動代碼和業務代碼分開,因此我們再新建一個app.js,app.js 代碼如下:

'use strict';

// 在應用中加載node模塊
const fs = require('fs');
const osenv = require('osenv');

// 引入 aysnc模塊
const async = require('async');
// 引入path模塊
const path = require('path');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 來獲取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判斷是否是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判斷是否是目錄
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模塊調用異步函數並收集結果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 創建模板實列的副本
  let clone = document.importNode(template.content, true);
  
  // 加入文件名及對應的圖標
  clone.querySelector('img').src = `images/${file.type}.svg`;
  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

// 該函數的作用是顯示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach(displayFile);
}

/*
 該函數的作用是:獲取到用戶個人文件夾的路徑,並獲取到該文件夾下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();

  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('對不起,您沒有加載您的home folder');
    }
    console.log(files);
    /*
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
    */
    inspectAndDescribeFiles(folderPath, files, displayFiles);
  });
}

window.onload = function() {
  main();
};

然后main.js 代碼如下:

'use strict';

// 引入 全局模塊的 electron模塊
const electron = require('electron');

// 創建 electron應用對象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定義變量 對應用視窗的引用 
let mainWindow = null;

// 監聽視窗關閉的事件(在Mac OS 系統下是不會觸發該事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   創建一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true
    }
  });

  // 添加如下代碼 可以調試
  mainWindow.webContents.openDevTools();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

如上代碼是目前所有的代碼了,我們運行下 electron . 命令后,可以看到如下所示:

如上圖可以看到我們的代碼有調試代碼了,那是因為在main.js加上了如下這句代碼:

// 添加如下代碼 可以調試
mainWindow.webContents.openDevTools();

並且如果我們按照之前的代碼,在main.js 實列化 BrowserWindow 的時候,如下實列化代碼:

mainWindow = new BrowserWindow();

如下代碼:

// 將index.html 載入應用視窗中
app.on('ready', () => {
  /*
   創建一個新的應用窗口,並將它賦值給 mainWindow變量。
  */
  mainWindow = new BrowserWindow();

  // 添加如下代碼 可以調試
  mainWindow.webContents.openDevTools();

  // 載入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 當應用被關閉的時候,釋放 mainWindow變量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

在控制台中會報如下的錯:

解決的方案就是加上如下配置:

mainWindow = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true
  }
});

這是因為最新的electron@5.0系列中,這個nodeIntegration參數,默認改成false了。
而在以前版本的electron中,這個nodeIntegration參數,默認為true。

github源碼查看


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM