• 使用JS开发桌面端应用程序NW.js-3-开发问题小记


    前言

    因为我们的项目是2C的,而XP系统是最大的用户量占比,所以只能使用nw开发而不能用Electron,本文谨记开发nw过程中遇到的各种问题以及解决方案

    nw.Window.open打开新窗口不能设定指定位置

    问题描述

    nw.Window.open打开新窗口API中的参数option中position字段只能指定为centermouse。如字面含义:center为屏幕正中央,mouse为鼠标当前位置。
    几乎可以推测,nw的鼠标右键菜单应该也是使用此接口,明显是为了弹出右键菜单用的,除此之外几乎没有别的应用场景可以用到新打开窗口在鼠标位置的。
    所以,在nw的打开新窗口功能中,其实可以说 只能显示在屏幕正中央。

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

    官网对position的描述:

    position
    {String} be null or center or mouse, 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

  • 相关阅读:
    OpenCV学习
    STL容器
    实践教学小程序(2022529)
    抖音下载 Elon
    抖音极速版下载 Elon
    vue脚手架快速搭建
    Vue elementUi组件库 input输入内容没有回显
    Django admin后台使用markdown
    Vue elementUi组件中使用下拉框,eldropdownitem @click事件无效
    MinGWw64 离线包安装方法
  • 原文地址:https://www.cnblogs.com/xxcanghai/p/7163438.html
Copyright © 2020-2023  润新知