转使用Sencha Cmd创建应用程序

相关的目录的结构

4.1 概述

从Ext JS 4.1.1a开始,为了配合Ext JS和Sencha Touch的开发,特意设计了Sencha Cmd这个跨平台的命令行工具。在此之前,这样的工具也是有的,名字为SDK Tools,但是它的作用和意义对开发来说显得并不突出。而从Ext JS 4.1.1a和Sencha Touch 2.1之后,Sencha Cmd的作用和意义就显得很突出了,它的主要作用包括以下两点。

  • 编译脚本:使用Sencha Cmd创建的应用程序,编写完成后,可通过Sencha 
    Cmd的生成(build)功能,将零散的应用程序脚本打包成为一个下载包,而且可以把没有使用到的组件清理出去。这样做的好处是显而易见的。如果不使用该功能,那么就必须在页面中包含ext-all.js文件,下载全部的Ext 
    JS组件,由于该文件比较大,会造成用户体验上的问题。在使用ext-all.js文件的同时,还需要使用动态加载功能,在需要的时候向服务器请求必要的脚本文件,在界面比较复杂,脚本文件比较多的情况下,就会发送大量的请求,这不单加重了服务器的负担,还严重影响了用户体验。使用生成功能后,则可将所有应用程序内的脚本打包为一个文件发布,这对于部署和用户体验都是有极大好处的。

  • 编译主题:这是Sencha Cmd的作用体现得最突出的一点。一直以来,Ext JS最突出的问题就是主题的缺乏,而主题的修改也不方便。从Ext JS 4开始,引入了Compass和Sass,这使得创建和修改主题更加便利,但是,要编译主题并兼顾自定义样式,还是一件很麻烦的事,而使用Sencha Cmd,则使事情变得简单了。在应用程序的开发过程中,就可以修改主题以及对样式进行定义,然后使用生成功能,在打包脚本的同时打包主题和样式。在Ext JS 6中引用了新的SASS编译器——Fashion,不再需要使用Compass和Sass了。

4.2 安装Sencha Cmd

4.2.1 运行环境配置

在编写本书时,笔者使用的Sencha Cmd版本为6.2.1.29,。如果你的版本太低,请使用sencha upgrade命令升级Cmd。如果升级到最新的版本出现生成错误,请去官方网站寻找6.2.1.29版本。笔者在编写本书的时候,碰到过由于Sencha Cmd版本同,造成生成出现错误的情况。 
由于新版的Sencha Cmd已经内置JRE安装和不再需要Compass,因而不再需要先安装JRE和Ruby了,这无疑减少了JRE版本和Ruby版本对Sencha Cmd的影响,减少了配置错误。 
Sencha Cmd的下载地址是https://www.sencha.com/products/extjs/cmd-download/,根据你机器所使用的平台下载相应的安装文件就可以了。笔者下载的是基于Windows的64位版本,下载的文件是SenchaCmd-6.2.1-windows-64bit.zip。

4.2.2 安装Sencha Cmd

将文件SenchaCmd-6.2.1-windows-64bit.zip解压后,会看到SenchaCmd-6.2.1.29-windows-64bit. 
exe文件,双击该文件将会看到如图 4 1所示的安装窗口。

单击Next按钮将会看到如图4-2所示的许可协议窗口。

选择I accept the agreement(我接受许可)之后,单击Next按钮,会看到如图4-3所示的设置安装目录窗口。 
在设置安装目录窗口,可将安装目录修改为适合自己的目录,笔者是安装在D:\Program Files (x86)\Sencha\Cmd目录的。目录修改完成后,单击Next按钮,会看到如图 4 4 所示的选择组件窗口。

在选择组件窗口中,可看到Compass扩展,如果还需要维护Ext JS 4或5的项目,可以选择装上,在这里就不安装了。单击Next按钮。进入如图 4 5 所示选择附加任务窗口。

在选择附加任务窗口中,主要的任务是将sencha命令添加到全局路径中,这样,在打开命令提示符窗口后,就不需要输入完整路径来运行命令。因而,这个里最好是选择Yes,让安装程序自己去添加全局路径。单击Next按钮后就进入安装了。 
安装结束后,单击Finish按钮完成安装。

4.2.4 验证安装

在Sencha Cmd安装以后,打开一个命令提示符窗口,并在窗口内输入sencha命令来验证安装是否成功。如果输入sencha命令后,在窗口内有输出如代码清单 4 1所示的信息,则说明安装已经成功。 
代码清单4-1 验证Sencha Cmd的安装

Sencha Cmd v6.2.1.29
Sencha Cmd provides several categories of commands and some global switches. In
most cases, the first step is to generate an application based on a Sencha SDK
such as Ext JS or Sencha Touch:

    sencha -sdk /path/to/sdk generate app MyApp /path/to/myapp

Sencha Cmd supports Ext JS 4.1.1a and higher and Sencha Touch 2.1 and higher.

To get help on commands use the help command:

    sencha help generate app

For more information on using Sencha Cmd, consult the guides found here:

http://docs.sencha.com/cmd/


Options
  * --beta, -be - Enable beta package repositories
  * --cwd, -cw - Sets the directory from which commands should execute
  * --debug, -d - Sets log level to higher verbosity
  * --info, -i - Sets log level to default
  * --nologo, -n - Suppress the initial Sencha Cmd version display
  * --plain, -pl - enables plain logging output (no highlighting)
  * --quiet, -q - Sets log level to warnings and errors only
  * --sdk-path, -sd - The location of the SDK to use for non-app commands
  * --strict, -st - Treats warnings as errors, exiting with error if any warnings are present
  * --time, -ti - Display the execution time after executing all commands

Categories
  * app - Perform various application build processes
  * compile - Compile sources to produce concatenated output and metadata
  * cordova - Quick init Support for Cordova
  * diag - Perform diagnostic operations on Sencha Cmd
  * framework - Commands to manage frameworks in the current workspace
  * fs - Utility commands to work with files
  * generate - Generates models, controllers, etc. or an entire application
  * manager - Commands for interacting with Sencha Web Application Manager.
  * manifest - Extract class metadata
  * package - Manages local and remote packages
  * phonegap - Quick init support for PhoneGap
  * repository - Manage local repository and remote repository connections
  * template - Commands for working with templates
  * theme - Commands for low-level operations on themes
  * web - Manages a simple HTTP file server
  * workspace - Commands to perform actions on the current workspace

Commands
  * ant - Invoke Ant with helpful properties back to Sencha Cmd
  * audit - Search from the current folder for Sencha frameworks and report their license
  * build - Builds a project from a legacy JSB3 file.
  * config - Load a properties file or sets a configuration property
  * help - Displays help for commands
  * js - Executes arbitrary JavaScript file(s)
  * switch - Manage the active Sencha Cmd version
  * upgrade - Upgrades Sencha Cmd
  * which - Displays the path to the current version of Sencha Cmd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

从代码清单 4 1可以看到,现在使用的Cmd版本是6.2.1.29。在版本号后面,紧接着列出的sencha语句,是用来创建应用程序的sencha语句。下面的另一句sencha 语句是用来查看帮助信息的。 
紧接着列出了Options、Categories和Commands三种命令参数。其中,Options是命令的可选参数,如-sdk,用来指定SDK的路径;Categories则是命令的类别,如app类别,配合相应的命令,可用来生成应用程序;而Commands则是具体的命令,如build,和app配合,用来生成应用程序。

4.2.5 语法

Sencha Cmd的基本语法规则如下: 
sencha [category] [command] [options…] [arguments…] 
虽然一般语法是这样,但也不是完全这样,例如用来创建应用程序的options项-sdk,就必须在category项之前,总之,用法比较灵活,也没统一规定,使用的时候需要注意。

4.3 创建应用程序

4.3.1 创建应用程序前要考虑的问题

在Ext JS 4,使用Cmd来创建应用程序,只有一种模式,而在Ext JS 6,将Sencha Touch合并到Ext JS后,就衍生出了通用应用程序、经典应用程序和现代应用程序等三种模式。经典应用程序就是之前的Ext JS应用程序;现代应用程序指的就是之前的Sencha Touch应用程序;通用应用程序,顾名思义,就是兼有经典应用程序和现代应用程序的应用程序。 
通用应用程序的目的很明确,就是打通桌面与移动设备之间的鸿沟,开发一套系统就能适用于所有平台,应用程序会根据设备来选择是运行经典应用程序还是现代应用程序。 
为什么要这么麻烦呢?为什么不能通过响应式布局来实现不同设备显示不同的视图,而要硬生生的将它们区分为经典应用程序和现代应用程序呢?笔者个人的理解是,要将Ext JS和Sencha Touch完全合并在一起,还有许多开发工作要做,是一项非常费时的浩大工程,譬如核心的融合、组件的融合以及主题的融合,都非一朝一夕能完成的,因而,采取渐进式的方式去逐步推进,是最有效的办法。Ext JS 6就是这种渐进式开发的第一个版本,先把核心部分融合在一起,以满足目前市场的需求。 
Ext JS 6的这种半融合模式,对开发人员来说,是具有相当大的挑战的。因为,为了区分经典模式视图和现代模式视图,Ext JS 6在应用程序目录架构上做了些许的修改。之前版本的应用程序目录架构只有一个app文件夹来存放应用程序的类文件,在新的架构下,添加了calssic和modern两个文件夹用来分别存放经典应用程序的类文件和现代应用程序的类文件。这样的划分虽然很清晰,但带来一个问题,Ext JS的自动加载机制根据类名找不到具体的文件位置了。造成这个问题的主要原因是自动加载机制会将命名空间默认指向app文件夹,当然,你也可以自己修改这个目录,但无论你怎么修改,也不可能将命名空间同时指向app、classic和morden这三个文件夹,从而造成无法加载类文件的错误。为了解决这个问题,Sencha采用了在bootstrap.json文件中,使用指定路径的方式来指定类的路径,这也就是为什么每次添加新的类后,都需要执行一次sencha app build或使用sencha app watch来监控应用程序,自动生成项目的原因。这项操作的主要作用就是更新bottstrap.json文件中类文件的路径。 
在使用Ext JS 6初期,笔者也是习惯从创建一个通用应用程序开始的,不过在开发过程中,由于类的路径问题搞得头晕脑胀的,最后还是放弃了。笔者所做的大部分项目都只是应用于桌面平台的,因而在后期,基本都只创建经典应用程序,间中会有些现代应用程序,也只是根据需要创建现代应用程序,不再使用通用应用程序进行开发,个中原因,主要是为了路径问题。 
以上说了那么多,主要目的是提醒大家,在选择Ext JS 6的开发模式上,需要谨慎应对。在不需要使用到通用应用程序的时候,就不要去使用通用应用程序的开发模式,正如本书的示例,不需要考虑现代应用程序这种模式,就只创建经典模式的应用程序。

4.3.2 开始创建应用程序

如果不清楚如何创建经典应用程序,可以在命令提示符窗口输入以下命令来获取帮助: 
sencha help generate app 
在显示的帮助信息中,会看到以下信息:

For Ext JS 6, by default, this application will be a Universal Application.
To override this and select a particular toolkit, you can use either of
these variations:

sencha generate app -ext -classic MyAppName ./MyAppPath
sencha generate app -ext -modern MyAppName ./MyAppPath
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过帮助信息的说明,可以知道,在默认情况下,创建的是通用应用程序,如果要指定工具包,可以使用classic或modern参数。 
下面来创建本书系统所需要使用到的经典模式应用程序。打开一个命令提示符窗口,使用cd命令切换到合适的文件夹,笔者是在D:\workspace文件夹下执行操作的。在workspace文件夹下,已经把Ext JS 6的框架文件解压到ext6文件夹了。在命令提示符窗口中输入以下命令来创建应用程序: 
sencha -sdk d:\workspace\ext6 generate app -classic SimpleCMS d:\workspace\SimpleCMS 
在以上命令中,sdk参数用来指定框架文件的位置。在app后面的SimpleCMS表示的是应用程序的名称,而在框架中,SimpleCMS也将是应用程序的命名空间,自定义组件的类名将以SimpleCMS为起始名称。最后的“d:\workspace\SimpleCMS”表示创建的应用程序将输出到workspace\SimpleCMS文件夹下。 
命令运行后,如果创建应用程序成功,可在命令提示符窗口看到代码清单 4 2所示的输出。如果出现了红色的“[ERR]”信息,那说明应用程序已经创建失败了,就需要根据“[ERR]”后的信息去找出失败的原因。 
代码清单 4 2 创建应用程序时的输出

Sencha Cmd v6.2.1.29
[INF] Copying framework to d:\workspace\SimpleCMS\ext
[INF] Using GPL version of Ext JS version 6.2.0.981 from d:\workspace\SimpleCMS\ext.
[INF] The implications of using GPL version can be found here (http://www.sencha.com/products/extjs/licensing).
[INF] Processing Build Descriptor : default
[INF] Loading app json manifest...
[INF] Appending content to d:\workspace\SimpleCMS\bootstrap.js
[INF] Writing content to d:\workspace\SimpleCMS\bootstrap.json
[INF] merging 237 input resources into d:\workspace\SimpleCMS\build\development\SimpleCMS\resources
[INF] merged 237 resources into d:\workspace\SimpleCMS\build\development\SimpleCMS\resources
[INF] merging 17 input resources into d:\workspace\SimpleCMS\build\development\SimpleCMS
[INF] merged 17 resources into d:\workspace\SimpleCMS\build\development\SimpleCMS
[INF] Writing content to d:\workspace\SimpleCMS\sass\example\bootstrap.json
[INF] Writing content to d:\workspace\SimpleCMS\sass\example\bootstrap.js
[INF] writing sass content to d:\workspace\SimpleCMS\build\temp\development\SimpleCMS\sass\SimpleCMS-all.scss.tmp
[INF] appending sass content to d:\workspace\SimpleCMS\build\temp\development\SimpleCMS\sass\SimpleCMS-all.scss.tmp
[INF] appending sass content to d:\workspace\SimpleCMS\build\temp\development\SimpleCMS\sass\SimpleCMS-all.scss.tmp
[INF] writing sass content to d:\workspace\SimpleCMS\build\temp\development\SimpleCMS\sass\config.rb
[INF] Writing content to d:\workspace\SimpleCMS\build\development\SimpleCMS\app.json
[LOG] Building d:/workspace/SimpleCMS/build/temp/development/SimpleCMS/sass/SimpleCMS-all.scss
[WRN] @theme-background-image: Theme image not found: images/menu/default-menubar-group-checked.png
Exiting with code 0
[INF] Appending content to d:\workspace\SimpleCMS\bootstrap.js
[INF] Writing content to d:\workspace\SimpleCMS\bootstrap.json
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从代码清单4-2中可以看到,sencha命令首先做的工作是把框架文件复制到d:\workspace\SimpleCMS\ext文件夹,然后创建bootstrap.js和bootstrap.json文件。接下了的代码是使用“sencha app build –development”命令生成了一次应用程序的输出代码,该过程的作用是创建应用程序调试所需的样式文件,把示例代码的类文件的路径写入bootstrap.json文件。在输出中有一个标记为“[WRN]”的警告,如果不是强迫症患者,不用介意,这个对应用程序没有影响,估计是框架自身的臭虫。 
应用程序创建后,可以把文件夹放到Web服务器下,来测试刚生成的应用程序。如果,一时找不到IIS或其他Web服务器,也可以使用Sencha Cmd的Web服务器功能来测试。在命令提示符窗口中输入以下命令: 
sencha fs web -port 8000 start -map d:\workspace\SimpleCMS 
在命令中,port选项的作用是定义访问端口,这里将使用8000作为端口,而map选项是用来指定Web的访问目录的。 
命令执行后,会在命令提示符窗口看到以下输出:

Sencha Cmd v6.2.1.29
[INF] Starting server on port : 8000
[INF] Mapping http://localhost:8000/ to d:\Workspace\SimpleCMS...
[INF] Server started at port : 8000
  • 1
  • 2
  • 3
  • 4
  • 5

从输出中可以看到,Web服务已经启动,启动的端口为8000,地址http://localhost:8000/已经映射到d:\Workspace\SimpleCMS文件夹了。现在可以通过http://localhost:8000/来访问应用程序了。 
在浏览器中打开http://localhost:8000/,会看到如图 4 6所示的页面效果。从图 4 6可以看到,Ext JS 6应用程序默认使用了现在比较流行的管理系统风格,而主题是海卫一主题。

4.4 应用程序的结构

4.4.1 目录结构

应用程序创建后的目录结构如图4-7所示,以下是这些目录和文件的说明。

  • .sencha文件夹:在该文件夹下有app和workspace两个文件夹,主要包含一些生成应用程序所需的配置文件。一般情况下,不需要修改.sencha文件夹下的任何文件。
  • app文件夹:这是后续开发将要使用到的文件夹。在该文件夹下有controller、model、store和view 4个文件夹,分别用来存放应用程序的控制器、模型、存储和视图文件。在app文件夹下,有一个application.js文件,该文件是用来加载视图、控制器和存储的,在后续开发中随着视图、控制器和存储的添加,需要不断修改该文件。在app\view文件夹下,已经生成了Viewport.js和Main.js两个主界面视图,后续的开发将从修改这两个文件开始。
  • ext文件夹:Ext JS的框架文件所在文件夹。
  • overrides文件夹:当需要重写框架的类时,可把存放在这里。
  • packages文件夹:用来放置打包后的文件。具体可参阅API的指南部分Sencha Cmd节点下关于Sencha Cmd Packages的介绍。
  • resources文件夹:存放应用程序的资源文件。
  • sass文件夹:存放自定义的主题或样式文件。
  • app.js:应用程序的启动文件。
  • app.json:应用程序的配置文件。
  • bootstrap.css:样式引导文件。代替直接加载ext-all.css或其他编译好的样式文件。该文件会在生成应用程序时被修改。
  • bootstrap.js:脚本引导文件,用于加载应用程序所需的脚本和样式文件。
  • bootstrap.json:用来保存应用程序中脚本和样式文件的路径,为bootstrap.js加载脚本和样式文件提供加载地址。
  • build.xml:生成应用程序时需要使用到的配置文件。
  • index.html:应用程序启动时需要使用到的页面文件。由于后续使用Asp.NET MVC 4来进行开发,因而需要将该文件内的内容转移到首页文件中。
  • workspace.json:一个大的项目,可能是有几个小项目组成,而这些小项目会有一些共享代码,这时候就需要用到工作空间(workspace)来解决共享代码的问题,而本文件就是用来定义共享空间的。在本书中不需要使用到这个,就不深入研究了。

4.4.2 index.html

文件index.html的代码如代码清单 4 3所示。从代码中可以看到,在index.html文件中只是加载了bootstrap.js文件,并没有加载其他脚本文件,其余脚本和样式文件的加载全靠bootstrap.js文件来实现,而bootstrap.js是通过bootstrap.json文件来获取加载的脚本和样式文件的。在开发通用应用程序的时候,了解这个很重要,这也是为什么开发通用应用程序需要经常生成的原因,需要不断的更新bootstrap.json,不然就会出现找不到类的错误。开发经典模式或者现代模式的应用程序就没有这个麻烦了,因为框架在找不到类的时候,会根据默认机制去app文件夹下找文件,不会出现找不到类的情况。当然,写错了类名还是会找不到类的。 
代码清单4-3 index.html

<!DOCTYPE HTML>
<html manifest="">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=10, user-scalable=yes">

    <title>SimpleCMS</title>

    <!--
    <script type="text/javascript">
        var Ext = Ext || {}; // Ext namespace won't be defined yet...

        // This function is called by the Microloader after it has performed basic
        // device detection. The results are provided in the "tags" object. You can
        // use these tags here or even add custom tags. These can be used by platform
        // filters in your manifest or by platformConfig expressions in your app.
        //
        Ext.beforeLoad = function (tags) {
            var s = location.search,  // the query string (ex "?foo=1&bar")
                profile;

            // For testing look for "?classic" or "?modern" in the URL to override
            // device detection default.
            //
            if (s.match(/\bclassic\b/)) {
                profile = 'classic';
            }
            else if (s.match(/\bmodern\b/)) {
                profile = 'modern';
            }
            else {
                profile = tags.desktop ? 'classic' : 'modern';
                //profile = tags.phone ? 'modern' : 'classic';
            }

            Ext.manifest = profile; // this name must match a build profile name

            // This function is called once the manifest is available but before
            // any data is pulled from it.
            //
            //return function (manifest) {
                // peek at / modify the manifest object
            //};
        };
    </script>
    -->

    <!-- The line below must be kept intact for Sencha Cmd to build your application -->
    <script id="microloader" data-app="53f4957a-885b-457f-98a2-0314927f7a39" type="text/javascript" src="bootstrap.js"></script>

</head>
<body></body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

4.4.3 bootstrap.css

在bootstrap.css文件内只有一句代码:

@import 'build/development/SimpleCMS/resources/SimpleCMS-all.css';
  • 1

从代码可以看到, 它引用了build目录下的样式文件,也就是在创建应用程序时,执行的那次生成所创建的样式文件,这也解释了为什么要在创建应用程序时需要执行一次生成操作。 
这样做的目的,还是为了适应通用应用程序的需要。因为经典模式的主题和现代模式的主题包是两个不同的包,再加上可能还需要分别为两种模式自定义一些样式,如何去加载这些样式,是一个问题。Sencha认为最好的解决办法就是生成一次应用程序,把所需的样式都打包到一个样式文件中,这样就不需要考虑加载多少文件的问题了,只需要加载一个样式文件就行了。 
新的样式引用方式,对于单独使用经典模式进行开发也有了新的要求,就是在引用一个组件的时候,可能该组件的样式还没打包进样式文件,这时候,就需要生成一次应用程序,将该组件的样式打包进样式文件。 
当发现视图中的某个组件,如日期选择字段,在显示时有问题的时候,就需要考虑是否已经在视图的requires中引用了该组件,并使用sencha app build命令生成了一次应用程序,以便将组件的样式打包到应用程序的样式文件,从而组件的正常显示。

4.4.4 bootstrap.js

文件bootstrap.js主要功能是用来加载应用程序的脚本和样式,如果在开发通用应用程序时经常找不到类文件,除了重新生成应用程序外,还可以通过修改该文件来实现。由于文件内容很多,在此就不多解释了,自己有兴趣的话可以研究一下。

4.4.5 application.js

文件application.js的代码如代码清单4-4所示,该文件与Ext JS 4的相比,没了controls和models配置项,主要原因是Ext JS 6的视图控制器是跟随视图创建的,不需要在这里预加载,而模型在存储中引用就行了,也不需要预加载。配置项stores的作用是预加载一些共享的存储。 
在该文件中,还多了一个onAppUpdate方法,该方法的作用是在应用程序的脚本文件发生改变的时候,通知用户更新应用程序。在这里需要做的是修改一下里面的提示信息。 
文件中的launch方法会在页面加载完成后执行,类似于jQuery的ready方法和Ext JS的onReady方法。在该方法内,可以做一些预处理。 
代码清单4-4 application.js

Ext.define('SimpleCMS.Application', {
    extend: 'Ext.app.Application',

    name: 'SimpleCMS',

    stores: [
        // TODO: add global / shared stores here
    ],

    launch: function () {
        // TODO - Launch the application
    },

    onAppUpdate: function () {
        Ext.Msg.confirm('Application Update', 'This application has an update, reload?',
            function (choice) {
                if (choice === 'yes') {
                    window.location.reload();
                }
            }
        );
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

4.4.6 app.js

文件app.js的内容如代码清单4-5所示。代码在requires中引用了主视图,并在配置项mainView中将主视图定义为SimpleCMS.view.main.Main,这样,在应用程序启动时,就会创建一个SimpleCMS.view.main.Main的实例,并渲染到页面中。 
代码清单4-5 app.js

Ext.application({
    name: 'SimpleCMS',

    extend: 'SimpleCMS.Application',

    requires: [
        'SimpleCMS.view.main.Main'
    ],

    mainView: 'SimpleCMS.view.main.Main'

});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4.4.7 SimpleCMS.view.main.Main类

类SimpleCMS.view.main.Main的内容如代码清单4-6所示。从代码可以看到,主视图是从Ext.tab.Panel 扩展的,也就是说主视图是一个标签页,在items中定义了Home、Users、Groups和Settings等4个标签。 
在配置项requires中,引用了插件Ext.plugin.Viewport,该插件的作用就是将当前视图作为视区(Viewport),整个页面的顶层组件就是当前视图。 
在配置项controller中,定义了视图的视图控制器,该视图控制器的别名为main,在代码清单4-7中可以看到它的定义“controller.main”。 
在配置项viewModel中,定义了视图的视图模型,该视图模型的别名为main,在代码清单4-8中可以看到它的定义“alias: ‘viewmodel.main’”。 
配置项controlle和viewModel在以后的开发中会经常使用到,因而这个要熟悉它是怎么使用的。 
从图4-6中可以看到,所看到的标签页与Ext JS示例中看到的标签页的样式不同,这是因为在这里使用了自定义的组件样式navigation(配置项ui)。这个组件的样式可以在sass\src\view\main文件夹下找到,在4.5节会讲述自定义样式这个问题。 
在视图中,还使用了响应式布局,这个是在responsiveConfig配置项中定义的。在定义中,tall的意思是当宽度小于高度的时候,标签(headerPosition)将显示在顶部(top),wide的意思是当宽度大于高度的时候,标签将显示在左边(left)。这个,大家可以通过浏览器的调试功能来查看,具体步骤是按F12键打开Web开发者工具,然后在右上角找到响应式设计模式,单击后就可切换不同的分辨率了。 
在视图中,还使用到了最简单的数据绑定功能,包括视图的标题(title)和子组件的内容(html)。 
代码清单4-6 SimpleCMS.view.main.Main

Ext.define('SimpleCMS.view.main.Main', {
    extend: 'Ext.tab.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.plugin.Viewport',
        'Ext.window.MessageBox',

        'SimpleCMS.view.main.MainController',
        'SimpleCMS.view.main.MainModel',
        'SimpleCMS.view.main.List'
    ],

    controller: 'main',
    viewModel: 'main',

    ui: 'navigation',

    tabBarHeaderPosition: 1,
    titleRotation: 0,
    tabRotation: 0,

    header: {
        layout: {
            align: 'stretchmax'
        },
        title: {
            bind: {
                text: '{name}'
            },
            flex: 0
        },
        iconCls: 'fa-th-list'
    },

    tabBar: {
        flex: 1,
        layout: {
            align: 'stretch',
            overflowHandler: 'none'
        }
    },

    responsiveConfig: {
        tall: {
            headerPosition: 'top'
        },
        wide: {
            headerPosition: 'left'
        }
    },

    defaults: {
        bodyPadding: 20,
        tabConfig: {
            plugins: 'responsive',
            responsiveConfig: {
                wide: {
                    iconAlign: 'left',
                    textAlign: 'left'
                },
                tall: {
                    iconAlign: 'top',
                    textAlign: 'center',
                    width: 120
                }
            }
        }
    },

    items: [{
        title: 'Home',
        iconCls: 'fa-home',
        // The following grid shares a store with the classic version's grid as well!
        items: [{
            xtype: 'mainlist'
        }]
    }, {
        title: 'Users',
        iconCls: 'fa-user',
        bind: {
            html: '{loremIpsum}'
        }
    }, {
        title: 'Groups',
        iconCls: 'fa-users',
        bind: {
            html: '{loremIpsum}'
        }
    }, {
        title: 'Settings',
        iconCls: 'fa-cog',
        bind: {
            html: '{loremIpsum}'
        }
    }]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

4.4.8 SimpleCMS.view.main.List

SimpleCMS.view.main.List类是在Home标签页中所显示的内容,它的代码如代码清单4-7所示。从代码可以看到,这是一个网格(扩展自Ext.grid.Panel),显示了Name 、Email和Phone等3列数据。 
在这里,要特别注意的是listeners配置项。在配置里,为网格的select事件绑定了onItemSelected方法,但是在类定义中并没有看到该方法,说明该方法的定义不是在自身的视图控制器中,就是在父类的视图控制器中。 
代码清单4-7 SimpleCMS.view.main.List

Ext.define('SimpleCMS.view.main.List', {
    extend: 'Ext.grid.Panel',
    xtype: 'mainlist',

    requires: [
        'SimpleCMS.store.Personnel'
    ],

    title: 'Personnel',

    store: {
        type: 'personnel'
    },

    columns: [
        { text: 'Name',  dataIndex: 'name' },
        { text: 'Email', dataIndex: 'email', flex: 1 },
        { text: 'Phone', dataIndex: 'phone', flex: 1 }
    ],

    listeners: {
        select: 'onItemSelected'
    }
});
4.4.9  SimpleCMS.view.main.MainController
视图控制器SimpleCMS.view.main.MainController的代码如代码清单4-8所示。在代码中,只定义了网格的select事件对应的onItemSelected方法,以及确认对话框的回调函数onConfirm。
代码清单4-8 SimpleCMS.view.main.MainController
Ext.define('SimpleCMS.view.main.MainController', {
    extend: 'Ext.app.ViewController',

    alias: 'controller.main',

    onItemSelected: function (sender, record) {
        Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this);
    },

    onConfirm: function (choice) {
        if (choice === 'yes') {
            //
        }
    }
});
4.4.10  SimpleCMS.view.main.MainModel
视图模型SimpleCMS.view.main.MainModel的代码如代码清单4-9所示。在代码中,定义了两个数据对象name和loremIpsum,分别用来作为视图的标题和子组件的显示内容。
代码清单4-9 SimpleCMS.view.main. MainModel
Ext.define('SimpleCMS.view.main.MainModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.main',

    data: {
        name: 'SimpleCMS',

        loremIpsum: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
    }

    //TODO - add data, formulas and/or methods to support your view
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

4.4.11 app.json

文件app.json的主要功能是为生成工作做一些配置,常用的配置包括首页文件的位置、所需包的设置、主题设置和附加脚本设置等。

1. 首页设置

要修改首页位置,可以在文件中寻找indexHtmlPath属性,它的值就是首页的路径和文件名。

2. 所需包的设置

在Ext JS 6中,类似图形与图表、扩展与字体图标等部件是以包的形式添加到应用程序中的。如果没有添加这些包,就使用不了包里所包含组件。在QQ群中,时常会有人问怎么在应用程序中的图表不显示、没有找到类文件等问题,大多数情况都是没有添加所需包造成的。 
要修改所需包的设置,可在文件中寻找requires属性,默认的代码如下:

"requires": [
        "font-awesome"
    ],
  • 1
  • 2
  • 3

从以上代码可以看到,默认情况下,已经把字体图标包包含在应用程序里了。如果使用的是海卫一主题,千万别删除这个包,因为海卫一主题的许多组件图标使用的都是字体图标,如果把这个包去了,图标就不显示了。 
如果想添加图形和图表包,在requires中添加“charts”就行了。要添加扩展组件包,可添加“ux”。 
在Ext JS 6中,本地化文件也是以包的形式提供的,包的引用只为“locale”。如果没有配置具体的本地化包,生成时会选择默认的包,其实是不使用任何本地化包。为了添加所需的语言包,还需要使用locale属性来指定单一的本地化包,或使用locales属性来指定多个本地化包,譬如,locale的值是 “zh_CN”,则使用简体中文作为本地化包。如果locales值是“[“zh_CN”,”zh_TW”]”,则使用简体中文和繁体中文作为本地化包。当设置的是locales时,如果在生成时没有指定本地化包,则会把数组定义中的第一个值作为打包时的本地化包,如“[“zh_CN”,”zh_TW”]”,默认会把简体中文作为本地化包。在生成时,可通过选项“–locale”来指定了打包时的本地化包,如要指定繁体中文作为本地化包,则生成的命令如下: 
sencha app build –l zh_TW 
一个包含了字体图标、简体中文的本地化包、扩展组件包和图形与图表包的配置代码如下:

"requires": [
    "font-awesome",
    "locale",
    "ux",
    "charts"
  ],
  "locale": "zh_CN",
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3. 主题设置

由于Ext JS 6使用的是生成后的样式文件,因而不能像Ext JS 4那样直接在页面中指定主题的样式文件,只能通过配置文件来指定主题包。 
要修改主题,可在文件中查找theme属性。它的默认值是theme-triton,如果要修改为Ext JS 4常用的海王星主题,可以将值修改为theme-neptune。 
如果想知道经典模式有哪些主题可用,可以进入ext\classic文件夹,以theme开头的文件夹就是可用的主题。

4. 附加脚本设置

在Ext JS 4,如果要添加第三方库,一般的处理就是通过SCRIPT标记来添加。而在Ext JS 6,多了一个选项,可以在app.json中添加。在app.json中添加的好处是不需要自己编写脚本来加载第三方库,通过bootstrap.json文件可自动加载第三方库。如果想更进一步,可以把第三放库也打包到生成的脚本文件中。 
要添加额外的脚本,可在文件中寻找js属性,默认的代码如下:

"js": [
        {
            "path": "${framework.dir}/build/ext-all-rtl-debug.js"
        },
        {
            "path": "app.js",
            "bundle": true
        }
    ],
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在代码中,已经包含了ext-all-rtl-debug.js和app.js两个脚本文件,其中的ext-all-rtl-debug.js是调试时使用到的框架包,而app.js则是应用程序的入口。 
大家可能注意到了,在app.js下,有一个bundle关键字,它的值为true表示该文件为连接类的容器。要理解连接类的容器,需要清楚的一点是,JavaScript是一个按文档流顺序执行的语言,也就是谁先加载的谁先执行。对于Ext JS这样存在多重继承的框架,根据规则,父类肯定要先加载,然后才能去加载子类、子类的子类,不然就会出现找不到父类的错误。还有一种情况就是在视图中使用了某个组件,如果视图先加载并创建了实例,而子组件的类文件还没有加载,也会出现找不到类的错误。因而,在Ext JS中,类之间哪个先加载,哪个后加载,是有顺序的。为了解决这个类加载的先后顺序问题,就促成了类似于C#的using语句,类似于JAVA的import语句这样的requires配置项。在requires中引用的类都是要先加载的,然后才能加载视图。虽然有了requires,但是这个requires该从什么地方开始算呢?这时候就需要一个入口容器来解决这个问题。在整个应用程序中,app.js是整个应用程序的入口,因而把它作为连接器的容器是最适合不过了,而bundle关键字就是用来解决这个问题。 
要把第三库加载到应用程序中,只要在js的数组内添加一个配置对象就行了。在配置对象内,使需要使用path属性来指定第三方库的路径,这个可参考ext-all-rtl-debug.js的定义。一般情况,如果没有特殊要求,建议将第三放库放在resource文件夹内,而不是放在其他文件夹内。这样的好处是,便于在app.json中指定路径。在发布时,也不需要额外的复制第三方库的文件,因为在复制resource文件夹时已经把第三库也一起复制了。 
如果要将第三库打包到最终的应用程序脚本文件中,可以添加includeInBundle属性,并设置为true。不过,在6.2这个版本中,第三库不能打包在Ext框架的顶部,只能打包在应用程序的后面,这就存在问题了,使用到这些第三方库的组件时会出错,因为找不到对象。如果使用的是6.2版本,建议还是不要打包了。 
不知道什么原因,Ext JS 6.2默认使用了ext-all-rtl-debug.js文件作为框架文件,而且该文件默认是右对齐的,从而让不少开发人员有点丈二和尚摸不着头脑。要解决这个问题其实很简单,将ext-all-rtl-debug.js修改为ext-all-debug.js就行了。

4.5 自定义样式

在创建的初始应用程序中,自定义了标签页的样式,这个是如何实现的呢? 
首先需要了解清楚的是目录结构。在应用程序的文件夹中,有一个名为sass的文件夹,该文件夹就是用来存放自定义的样式文件的。在该文件夹下有两个自定义样式文件经常会使用到的文件夹var和src,其中var文件夹是用来存放自定义的CSS变量的,而src文件夹用来存放自定义的样式文件。如果要查找某个组件有什么CSS变量,可参阅该组件的API文档中的theme variables节或theme mixins节。 
无论是src文件夹还是var文件,都需要遵循一个规则,就是与app\view文件夹中视图的目录结构相同且文件名(不包含扩展名)相同,例如初始应用程序中,要为主视图自定义样式,就需要在sass\src\view\main文件夹中创建Main.scss文件,并在该文件中定义样式,如果要为样式定义CSS变量,则需要在sass\var\view\main文件夹中创建Main.scss文件,并在该文件中定义CSS变量。 
接下来需要了解的是如何为组件定义样式,打开sass\src\view\main\Main.scss文件,会看到如代码清单 4 10所示的代码。 
代码清单 4 10 sass\src\view\main\Main.scss

/**
 * Generates a set of style rules for the "navigation" tab UI.
 */
@include extjs-tab-panel-ui(
    $ui: 'navigation',
    $ui-tab-background-color: transparent,
    $ui-tab-background-color-over: #505050,
    $ui-tab-background-color-active: #303030,
    $ui-tab-background-gradient: 'none',
    $ui-tab-background-gradient-over: 'none',
    $ui-tab-background-gradient-active: 'none',
    $ui-tab-color: #acacac,
    $ui-tab-color-over: #c4c4c4,
    $ui-tab-color-active: #fff,
    $ui-tab-glyph-color: #acacac,
    $ui-tab-glyph-color-over: #c4c4c4,
    $ui-tab-glyph-color-active: #fff,
    $ui-tab-glyph-opacity: 1,
    $ui-tab-border-radius: 0,
    $ui-tab-border-width: 0,
    $ui-tab-inner-border-width: 0,
    $ui-tab-padding: 24px,
    $ui-tab-margin: 0,
    $ui-tab-font-size: 15px,
    $ui-tab-font-size-over: 15px,
    $ui-tab-font-size-active: 15px,
    $ui-tab-line-height: 19px,
    $ui-tab-font-weight: bold,
    $ui-tab-font-weight-over: bold,
    $ui-tab-font-weight-active: bold,
    $ui-tab-icon-width: 24px,
    $ui-tab-icon-height: 24px,
    $ui-tab-icon-spacing: 5px,
    $ui-bar-background-color: #404040,
    $ui-bar-background-gradient: 'none',
    $ui-bar-padding: 0,
    $ui-strip-height: 0
);

/**
 * Generates a set of style rules for the "navigation" panel UI.
 */
@include extjs-panel-ui(
    $ui: 'navigation',
    $ui-header-color: #fff,
    $ui-header-glyph-color: #fff,
    $ui-header-glyph-opacity: 1,
    $ui-header-font-size: 20px,
    $ui-header-line-height: 24px,
    $ui-header-font-weight: bold,
    $ui-header-icon-height: 24px,
    $ui-header-icon-width: 24px,
    $ui-header-icon-spacing: 15px,
    $ui-header-background-color: $base-color,
    $ui-header-padding: 0,
    $ui-header-text-margin: 36px,
    $ui-header-noborder-adjust: false
);

.x-title-icon-navigation {
    font-family: FontAwesome;
    color: #fff;
    font-size: 24px;
    line-height: 24px;
}

.x-tab-icon-el-navigation {
    font-family: FontAwesome;
    color: #acacac;

    .x-tab-over & {
        color: #c4c4c4;
    }

    .x-tab-active & {
        color: #fff;
    }
}

.x-panel-header-title-navigation > .x-title-text-navigation:after {
    top: 30px;
    right: -24px;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

在代码中,在第一个代码段内包含了extjs-tab-panel-ui这个变量,在标签面板(Ext.tab.Panel)的API文档中,在theme mixins中可找到它的说明。根据说明,可以知道$ui是用来指定ui的名字的,当前的ui名字为navigation,也就是说,在组件中,将配置项ui的值指定为navigation就可以使用这个自定义的ui了。至于其他定义,基本上是标签页的具体样式定义了。 
在第二个代码段定义的是extjs-panel-ui,也就是说,这里定义的是面板(Ext.panel.Panel)的样式,在面板的theme mixins中可找到它的说明。 
第三和第四个代码段的代码我们应该很熟悉了,是常用的CSS样式定义语句。 
在第三个代码段中定义了标签的图标,在这里使用了字体图标,字体的颜色为acacac。鼠标移动到标签上时,字体颜色将转换为c4c4c4。当标签为当前活动标签时,字体颜色将转换为fff。 
在第四个代码段中,调整了标题的位置。 
以上是自定义样式的一些基本原则,也可以不必完全遵守,例如,可以在父视图中为子视图定义样式,但必须满足一个条件,就是编译器能够根据视图找到样式并打包进样式文件,如创建了一个没有视图与之对应的样式文件,那么编译器就不会把该样式文件的样式编译进样式文件中。 
有些自定义样式可能会在多个视图中使用,但并不需要每个视图都定义一次,因为,这些样式只要定义一次,并编译进样式文件,这个样式就可重复使用。视图与样式文件对应的目的是为了便于维护,便于找到视图所对应的样式。不过,按照这个道理,如果不嫌粘贴复制麻烦的话,每个视图都定义一次似乎更有利于查找样式。

4.6 生成应用程序

要生成应用程序很简单,在命令提示符窗口中,先进入到应用程序的根文件夹,然后输入以下命令即可: 
sencha app build 
在生成过程中,如果没有出现任何“[ERR]”开头的信息,说明生成成功了。生成成功后,可在build\production\SimpleCMS文件夹中找到生成后文件。 
如果在使用生成后的文件调试时出现“c is not a constructor return new c(a[0])”这样的错误,说明在某个视图中少引用了某个类或某个类的类名写错了,这时候,最简单直接的方法是使用以下命令生成一个测试版本来调试错误:

sencha app build --testing
  • 1

以上命令会生成类似于ext-all-debug.js这样的,没有将脚本压缩的应用程序,非常便于调试应用程序。这时候再调试应用程序,就可知道错误发生在哪里了。

4.7 关于乱码

使用Sencha Cmd创建的文件,不知道为什么不是以UTF-8编码格式保存的,当文件带有中文的时候,就会出现乱码。要解决这个问题不难,把文件另存为为UTF-8编码格式的文件就行了。在Visual Studio中的操作是,在主菜单选择文件→另存为打开“另存文件为”对话框。在对话框单击保存按钮的下箭头,在菜单中选择编码保存。这时会提示是否替换文件,选择是之后,会弹出高级保存选项对话框。在高级保存选项对话框中,在编码下拉选择中,选择“Unicode(UTF-8 无签名) - 代码页 65001”,最后单击确定按钮结束操作。 
如果还是不行,就尝试在脚本文件的顶部添加以下代码来声明该文件是UTF-8编码格式的文件:

//@charset UTF-8
  • 1

如果以上方法都不能解决编码问题,其实在与QQ群友的互动中也碰到这种情况,那就很可能是系统问题了,因为群友在其它机器上尝试是没问题的,就是他机器有问题。至于最终怎么解决,你懂的!

4.8 小结

本章主要讲述了如何使用Sencha Cmd来创建和生成应用程序。虽然整个过程对于新手来说,会有点困难,毕竟要安装的东西比较多,而且是文本模式操作,但是,这对于开发来说却是相当有帮助的,尤其是生成后的应用程序,既可以移除不需要的组件,以减少发布包的大小,还可以减少后续的动态加载,让应用程序的效率更高。 
虽然Sencha Cmd挺方便的,但是当发生错误的时候,也是很让人头疼的,尤其是跟踪和查找错误比较困难,因而,在使用之前,需要有心理准备。尤其是在Sencha Cmd版本与Ext JS版本对应不上的时候,出现的错误,可以说是根本解决不了,而且也是解决问题的盲点。造成这个问题的主要原因是版本不匹配的时候并不会有任何提示信息,只能自己去互联网寻找答案。为了解决这个问题,很多时候是不厌其烦的去测试,而并不会考虑到是版本不对问题,这时候,心可真是哇凉哇凉的。 
在下一章,将进入真正的开发阶段,开始使用Visual Studio 2017来创建简单的CMS系统。


猜你喜欢

转载自blog.csdn.net/qq_36573925/article/details/80520311