使用JS開發桌面端應用程序NW.js-3-開發問題小記

前言

由於咱們的項目是2C的,而XP系統是最大的用戶量佔比,因此只能使用nw開發而不能用Electron,本文謹記開發nw過程當中遇到的各類問題以及解決方案html

nw.Window.open打開新窗口不能設定指定位置

問題描述

nw.Window.open打開新窗口API中的參數option中position字段只能指定爲centermouse。如字面含義:center爲屏幕正中央,mouse爲鼠標當前位置。
幾乎能夠推測,nw的鼠標右鍵菜單應該也是使用此接口,明顯是爲了彈出右鍵菜單用的,除此以外幾乎沒有別的應用場景能夠用到新打開窗口在鼠標位置的。
因此,在nw的打開新窗口功能中,其實能夠說 只能顯示在屏幕正中央。node

nw.Window.open('http://xxcanghai.cnblogs.com/', {
    //打開新窗口的參數Option
    width:500,
    height:500,
    show:true,//是否顯示新窗口
    position:"center"//新窗口顯示位置,只能使用center或mouse
}, function(new_win) {
  console.log('已打開新窗口');
});

官網對position的描述:git

position
{String} be null or center or mouse, controls where window will be putgithub

nw.Window.open文檔:http://docs.nwjs.io/en/latest/References/Window/#windowopenurl-options-callback
Window Subfields窗口屬性position字段文檔:http://docs.nwjs.io/en/latest/References/Manifest%20Format/#positionchrome

解決方案

一句話就是,先open一個隱藏窗口,以後在callbcal裏面再重設其位置,再顯示出來shell

詳細步驟:
一、從新封裝nw.Window.open方法,在原有的position字段上擴充四個屬性,分別是 左上角,左下角,右上角,右下角,這裏使用枚舉對象來定義。npm

/**
* 擴充打開新窗口參數
* 
* @export
* @interface openWindowOption
* @extends {NWJS_Helpers.WindowOpenOption}
*/
export interface openWindowOption extends NWJS_Helpers.WindowOpenOption {
   /**
    * 控制打開的新窗口的所在位置
    */
   position?: "left_top" | "left_bottom" | "right_top" | "right_bottom" |
   "center" | "mouse" | null;
}
/**
* 打開一個新窗口
* 
* @export
* @param {string} url 新窗口的url
* @param {openWindowOption} [option] 新窗口的參數
* @param {(new_win?: NWJS_Helpers.win) => void} [callback] 打開成功後的回調函數,返回新窗口的nwWindow對象
*/
export function openWindow(url: string, option: openWindowOption = {}, callback: (new_win?: NWJS_Helpers.win) => void = function () { }) {
   /** 新增支持的窗口位置值,左上角,左下角,右上角,右下角 */
   enum winPositionEnum {
       left_top = "left_top",
       left_bottom = "left_top",
       right_top = "right_top",
       right_bottom = "right_bottom"
   };
   
   /** 新窗口位置的4個英文字符串數組 */
   const winPosiEnumArr: string[] = Object.keys(winPositionEnum);
}

二、雖然擴充了默認position屬性,但真正傳給nw的還得是他支持的,因此增長判斷若是使用的是新增字段,則保存用戶自定義設置,同時改寫option參數。
除此以外,由於要統一隱藏窗口,因此還要改寫默認的show屬性,保存用戶設定的是否顯示窗口,隱藏打開窗口後,當設定完位置後再手動設置用戶初始設定的show選項。windows

option = Object.assign(<openWindowOption>{
  show: true
}, option);

/** 用戶傳過來的窗口位置參數字符串 */
var optPosi: string = "";

//若是用戶傳過來的position參數爲我自定義的,則移除原有值
if (typeof option.position === "string" && winPosiEnumArr.indexOf(option.position) >= 0) {
  optPosi = option.position;
  delete option.position;
}

/** 用戶傳過來的窗口是否隱藏選項 */
var optShow: boolean = option.show;
option.show = false;

三、執行真正的nw.Window.open,同時在打開後的callback中根據自定義位置選項從新設定窗口位置。
最後再還原用戶本來設定的show屬性,以及觸發用戶本來傳進來的callback回調函數。api

nw.Window.open(url, option, function (nwWin: NWJS_Helpers.win) {
  /** 打開的隱藏窗口的寬度和高度 */
  var { width, height } = nwWin;
  
  /** 獲取第一個顯示器對象 */
  const screen: NWJS_Helpers.screen = nw.Screen.screens[0];
  
  /** 獲取顯示器的可用工做區域 */
  const area = screen.work_area;
  
  /** nw的chrome殼子的四個邊框高度 */
  const border = {
      left: 5 * screen.scaleFactor,
      right: 5 * screen.scaleFactor,
      top: 24 * screen.scaleFactor,
      bottom: 5 * screen.scaleFactor
  }
  if (optPosi.length > 0) {
      let x: number = nwWin.x;
      let y: number = nwWin.y;
      if (option.frame == undefined || option.frame == true) {
          width += border.left + border.right;
          height += border.top + border.bottom;
      }
      if (optPosi == winPositionEnum.left_top) {
          x = 0;
          y = 0;
      } else if (optPosi == winPositionEnum.left_bottom) {
          x = 0;
          y = area.height - height;
      } else if (optPosi == winPositionEnum.right_top) {
          x = area.width - width;
          y = 0;
      } else if (optPosi == winPositionEnum.right_bottom) {
          x = area.width - width;
          y = area.height - height;
      }

      nwWin.x = Math.round(x);
      nwWin.y = Math.round(y);
  }
  //還原用戶默認設定是否顯示窗口
  if (optShow) {
      nwWin.show();
  }
  //觸發用戶傳進來的callback
  return callback.apply(this, Array.prototype.slice.call(arguments));
});

四、由於我只須要在四個角顯示,因此只擴充了4個枚舉類型,若是須要在指定座標(x,y)顯示窗口,以上同理增長對position的對象類型{x:number,y:number}檢測處理便可。數組

nw的系統API-openExternal在XP系統下沒法打開本地磁盤路徑

問題描述

nwjs官方提供了有關Shell相關的API,提供了簡單桌面相關操做的接口。之所說他簡單,是由於簡直太太太簡單甚至寒酸了,只有3個API分別是:
Shell.openExternal(uri) 打開外部連接;
Shell.openItem(file_path) 使用系統默認打開方式打開文件;
Shell.showItemInFolder(file_path) 和在資源管理器中顯示某文件
官方文檔:http://docs.nwjs.io/en/latest/References/Shell/

// Open URL with default browser.
nw.Shell.openExternal('https://github.com/nwjs/nw.js');

其中openExternal接口可使用系統默認瀏覽器打開連接,也可使用系統資源管理器打開某本地磁盤路徑文件夾。此接口在Win7系統下沒有問題,可是在XP系統下沒法打開本地磁盤路徑。
緣由未知!

解決方案

作操做系統類型判斷,在XP系統下利用child_process.exec方法,執行系統cmd命令行:start explorer + 路徑來解決。

// 導入操做系統信息模塊
import os = require("os");
// 導入子進程模塊
import child_process = require("child_process");

/**
* 打開文件夾或用默認瀏覽器打開網頁連接
* 
* @param {string} uri 文件夾路徑或網頁連接
*/
export function openExternal(uri: string): void {
   if (typeof uri !== "string" || uri.length == 0) {
       return null;
   }
   var isxp = (os.type() === "Windows_NT") && (os.release().indexOf("5") >= 0);
   if (isxp) {
       child_process.exec("start explorer " + uri);// XP下使用explorer打開文件夾或網頁
   } else {
       return nw.Shell.openExternal(uri);
   }
}

關於Nodejs的OS模塊

主要提供獲取操做系統的各種信息,此處使用了os.type()os.release()兩個方法。

os.type() 方法返回一個字符串,代表操做系統的名字,'Linux'表示在 Linux系統上, 'Darwin' 表示在 macOS 系統上,'Windows_NT' 表示在 Windows系統上。

os.release() 方法返回一個字符串, 指定操做系統的發行版。

關於windows系統的發行版本號

其中:
"5.0.*"爲windows 2000系統;
"5.1.*"爲windows XP系統;
"5.2.*"爲windows XP 64位以及windows Server 2003系統
因此上述代碼中作了只要以"5"開頭的都匹配。

關於版本號對應詳細操做系統詳見:https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions

關於Nodejs執行系統命令行

首先使用了Nodejs的核心模塊:child_process子進程模塊,其中的exec方法。對此官方對exec方法的描述是:

child_process.exec(command[, options][, callback])

command 要運行的命令,用空格分隔參數
options < Object> 參數
callback 當進程終止時調用,並帶上輸出。
衍生一個 shell,而後在 shell 中執行 command,且緩衝任何產生的輸出。

此處的shell在windows系統上就是cmd.exe命令行(命令提示符)。
關於NodeJs如何在windows系統上執行.bat和.cmd批處理文件官網有更加詳細的解釋:http://nodejs.cn/api/en/child_process.html#child_process_spawning_bat_and_cmd_files_on_windows

關於windows系統內置命令-start

此處使用了windows系統命令提示符的系統內置命令:start
他能夠用來啓動各類內部命令,也能夠啓動外部應用程序。此處啓動了explorer就屬於全局外部應用程序。
關於start命令的用法與解釋:(能夠在命令行中使用start/?得到)

啓動一個單獨的窗口運行指定的程序或命令

START ["title"] [/D path] [/I] [/MIN] [/MAX][command/program] [parameters]

"title" 在窗口標題欄中顯示的標題
path 啓動目錄。新的環境將是傳遞給 cmd.exe 的原始環境,而不是當前環境
MIN 以最小化方式啓動窗口
MAX 以最大化方式啓動窗口
...省略...

在此處我使用的是用start命令啓動explorer程序。

關於windows系統的explorer.exe

explorer.exe是Windows程序管理器或者文件資源管理器,它用於管理Windows圖形殼。

簡單的來說,explorer就是咱們的桌面,打開的全部磁盤或文件夾的應用程序。利用他能夠實現:
一、使用系統註冊的默認方法打開某文件。
二、打開本地磁盤路徑某文件夾。
三、使用系統默認瀏覽器打開url地址。
四、以及調用任何在系統裏註冊過的各種協議地址,如ftp://或是mailto:***等並用其註冊的應用程序打開。

而在explorer後面跟着文件夾地址便可實現使用資源管理器打開目錄,以及打開網頁連接

explorer的其餘參數詳解:

Explorer.exe

Command-line switches that you can use to open the GUI Windows Explorer (Explorer.exe).

Options
/e Open Windows Explorer in its default view.
(,)/root,object Open the specified object in a window view.
/select,object Open a window view with the specified folder, file or application selected.
/separate Launch the explorer instance as a separate process.
(This is an undocumented feature)

explorer.exe命令行參數詳見:https://ss64.com/nt/explorer.html
Explorer.exe Command-Line Switches:http://www.infocellar.com/software/explorer-switches.htm
某篇中文介紹:http://blog.csdn.net/ycool1984/article/details/387569

因此解決方案就一句話就是:用Nodejs啓動命令行,命令行啓動start命令,start啓動explorer.exe程序,explorer打開目錄磁盤路徑

Node.js的child_process.exec執行命令的返回值中文亂碼

問題描述

Nodejs的子線程模塊child_process的執行系統命名的接口exec,當命令返回全部非中文字符時都會亂碼。
如執行一個date /t的命令顯示當前日期時間代碼:

child_process.exec("date /t", {}, function (error: Error, stdout: string, stderr: string) {
  console.log(stdout);
})

正常應該返回:

但實際上返回了:

經查NodeJs默認使用了UTF-8編碼,而中文操做系統的命令行的返回流均爲GB2312編碼,而在流轉字符串時再使用UTF-8解碼就致使了亂碼,並且沒辦法還原。

解決方案

一、先經過child_process.exec方法的option參數中的encoding字段設定爲"base64"。另其返回值不包含亂碼

child_process.exec("date /t", {
  encoding : "base64"//設定base64編碼
}, function (error: Error, stdout: string, stderr: string) {
  console.log(stdout);
})

效果以下:

二、再經過一個Node編解碼模塊iconv-lite,將返回值字符串再用base64編碼,最後用GB2312解碼。

import iconv = require("iconv-lite");
child_process.exec("date /t", {
  encoding:"base64"
}, function (error: Error, stdout: string, stderr: string) {
  console.log("解碼前:",stdout);
  stdout = iconv.decode(iconv.encode(stdout, 'base64'), 'gb2312');
  console.log("解碼後:",stdout);
})

以下圖,解決了中文亂碼問題:

關於更多iconv-lite的介紹:https://www.npmjs.com/package/iconv-lite

相關文章
相關標籤/搜索