前言
因為我們的項目是2C的,而XP系統是最大的用戶量占比,所以只能使用nw開發而不能用Electron,本文謹記開發nw過程中遇到的各種問題以及解決方案
nw.Window.open打開新窗口不能設定指定位置
問題描述
nw.Window.open
打開新窗口API中的參數option中position
字段只能指定為center
或mouse
。如字面含義:center
為屏幕正中央,mouse
為鼠標當前位置。
幾乎可以推測,nw的鼠標右鍵菜單應該也是使用此接口,明顯是為了彈出右鍵菜單用的,除此之外幾乎沒有別的應用場景可以用到新打開窗口在鼠標位置的。
所以,在nw的打開新窗口功能中,其實可以說 只能顯示在屏幕正中央。
nw.Window.open('http://xxcanghai.cnblogs.com/', {
//打開新窗口的參數Option
width:500,
height:500,
show:true,//是否顯示新窗口
position:"center"//新窗口顯示位置,只能使用center或mouse
}, function(new_win) {
console.log('已打開新窗口');
});
官網對position的描述:
position
{String}
benull
orcenter
ormouse
, controls where window will be put
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/#position
解決方案
一句話就是,先open一個隱藏窗口,之后在callbcal里面再重設其位置,再顯示出來
詳細步驟:
1、重新封裝nw.Window.open方法,在原有的position字段上擴充四個屬性,分別是 左上角,左下角,右上角,右下角,這里使用枚舉對象來定義。
/**
* 擴充打開新窗口參數
*
* @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);
}
2、雖然擴充了默認position屬性,但真正傳給nw的還得是他支持的,所以增加判斷如果使用的是新增字段,則保存用戶自定義設置,同時改寫option參數。
除此之外,因為要統一隱藏窗口,所以還要改寫默認的show屬性,保存用戶設定的是否顯示窗口,隱藏打開窗口后,當設定完位置后再手動設置用戶初始設定的show選項。
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;
3、執行真正的nw.Window.open,同時在打開后的callback中根據自定義位置選項重新設定窗口位置。
最后再還原用戶原本設定的show屬性,以及觸發用戶原本傳進來的callback回調函數。
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、因為我只需要在四個角顯示,所以只擴充了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就是我們的桌面,打開的所有磁盤或文件夾的應用程序。利用他可以實現:
1、使用系統注冊的默認方法打開某文件。
2、打開本地磁盤路徑某文件夾。
3、使用系統默認瀏覽器打開url地址。
4、以及調用任何在系統里注冊過的各類協議地址,如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
解碼就導致了亂碼,而且沒辦法還原。
解決方案
1、先通過child_process.exec
方法的option
參數中的encoding
字段設定為"base64"
。另其返回值不包含亂碼
child_process.exec("date /t", {
encoding : "base64"//設定base64編碼
}, function (error: Error, stdout: string, stderr: string) {
console.log(stdout);
})
效果如下:

2、再通過一個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