由於咱們的項目是2C的,而XP系統是最大的用戶量佔比,因此只能使用nw開發而不能用Electron,本文謹記開發nw過程當中遇到的各類問題以及解決方案html
nw.Window.open
打開新窗口API中的參數option中position
字段只能指定爲center
或mouse
。如字面含義: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}
benull
orcenter
ormouse
, 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}
檢測處理便可。數組
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); } }
主要提供獲取操做系統的各種信息,此處使用了os.type()
和os.release()
兩個方法。
os.type()
方法返回一個字符串,代表操做系統的名字,'Linux'
表示在 Linux系統上, 'Darwin'
表示在 macOS 系統上,'Windows_NT'
表示在 Windows系統上。
os.release()
方法返回一個字符串, 指定操做系統的發行版。
其中:
"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的核心模塊: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
。
他能夠用來啓動各類內部命令,也能夠啓動外部應用程序。此處啓動了explorer就屬於全局外部應用程序。
關於start命令的用法與解釋:(能夠在命令行中使用start/?
得到)
啓動一個單獨的窗口運行指定的程序或命令
START ["title"] [/D path] [/I] [/MIN] [/MAX][command/program] [parameters]
"title" 在窗口標題欄中顯示的標題
path 啓動目錄。新的環境將是傳遞給 cmd.exe 的原始環境,而不是當前環境
MIN 以最小化方式啓動窗口
MAX 以最大化方式啓動窗口
...省略...
在此處我使用的是用start命令啓動explorer
程序。
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打開目錄磁盤路徑
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