使用命令行安装FastAdmin的详细步骤

1. 为什么要写这篇文章

  • 首先,FastAdmin是个好东西,值得一用和一学。
  • 第二,官方推荐使用命令行安装FastAdmin,因为采用命令行安装的方式可以和FastAdmin随时保持更新同步。
  • 官方默认你已经对以下这些知识和概念有了解:PHP、Git、Node.js、Composer、Bower。然而事实情况是,很多人只是了解PHP,对其它几个技术不甚了解,或者说的直白点,压根没听过。其实这不是什么丢人的事情,因为对于做中小项目的PHPer而言,搞清楚PHP的常规用法就足够了。
  • 写这篇文章的一个主要意图是通过安装过程让大家明白Git、Node.js、Composer、Bower这四个东东都是做什么的。

2. 官方给出的安装说明

强烈建议使用命令行安装,因为采用命令行安装的方式可以和FastAdmin随时保持更新同步。使用命令行安装请提前准备好Git、Node.js、Composer、Bower环境,Linux下FastAdmin的安装请使用以下命令进行安装。
1. 克隆FastAdmin到你本地
git clone https://gitee.com/karson/fastadmin.git
2. 进入目录
cd fastadmin
3. 下载前端插件依赖包
bower install
4. 下载PHP依赖包
composer install
5. 一键创建数据库并导入数据
php think install -u 数据库用户名 -p 数据库密码
6. 添加虚拟主机并绑定到项目的public目录
7. 为了安全,安装完成后会在public目录生成随机后台入口,请通过随机后台入口登录管理后台

3. 分步详细解释

3.1 克隆FastAdmin到你本地

  • git是什么?git是和svn一样的版本管理工具。目前git主要应用的场景还是在开源领域。对于类似FastAdmin这样的开源项目而言,一般都是第一时间将最新的代码放到git的服务器上。通过git clone命令,就可以从git服务器下载最新的项目代码。
  • 这里面的git clone命令是运行在git bash客户端里面的。而git bash则需要在电脑里面安装git软件后才能使用。
  • git软件的官方网址如下:https://git-scm.com/ ,下载安装步骤和普通软件无异,基本上就是【下一步】就可以了。
  • git安装好后,在任何地方点击鼠标右键都会出现Git Bash Here命令选项,点击Git Bash Here后,就会出现git bash小黑窗。
  • 在哪里打开git bash小黑窗,git bash的当前目录就被确定在哪里。
    file
  • 所以呢,在你想要安装FastAdmin的目录下打开Git Bash小黑窗,然后运行命令,就可以将最新的FastAdmin代码下载下来了(注意:下载时会创建一个新的名为FastAdmin的目录):
    git clone https://gitee.com/karson/fastadmin.git

3.2 进入目录

待所有代码都下载完成后,打开一个cmd窗口,然后进入到刚刚生成的fastadmin目录中:

cd fastadmin

向下承接【A】(如果你的电脑已经有Node.js和bower环境)

3.3 下载前端插件依赖包

3.3.1 先安装npm

你想要使用bower的前提是你必须在机器上安装bower。而安装bower的时候需要使用npm,不仅是bower,下面的less也需要用npm来安装。
npm来自哪里呢?来自Node.js!
是不是快绕晕了?没事儿,再梳理下:

  1. 先在本机安装Node.js。具体安装方法见:http://www.moonlightgate.com/archives/278
  2. 我们安装Node.js其实不是为了用Node.js本身,而是为了用安装Node.js时候自动附带安装的npm(有点“买椟还珠”的意思)。有关npm命令的详细介绍,可以参考:http://www.moonlightgate.com/archives/359
  3. npm怎么用呢?打开cmd窗口,直接输入相关的npm命令即可。
3.3.2 理解bower是什么?
  • 先说一句以便更好理解:bower和下面的composer的用处是非常相似的。只不过bower用于管理前端资源,composer用于管理后端php资源。
  • 那么bower是什么呢?bower是一个前端资源的管理器。
  • 举个例子:你在开发网站的时候,会用到很多前端的库,比如:Jquery、Bootstrap、Less等等。在没有bower之前,这些东西你都要自己一个个下载好,然后放到你的项目目录里面。这样做一来麻烦,二来下载的版本很可能每次都不一样,容易出现版本兼容的问题。有了bower后,bower会在你的项目根目录保存一个bower.json文件,里面写明白了这个项目需要用到哪些前端库以及这些库的版本号。这样只需要运行bower命令,就可以自动将bower.json里面提到的前端库自动从bower的服务器上下载下来并放到指定的位置。这种管理机制是非常有用的。
3.3.3 安装bower

打开cmd窗口,输入以下命令:

cnpm install bower -g 
3.3.4 使用bower下载前端插件依赖包

向上承接【A】

bower install

这样FastAdmin依赖的前端资源就都安装好了。
向下承接【B】(如果你的电脑已经有composer环境)

3.4 下载PHP依赖包

3.4.1 安装composer

上面讲bower的时候也提到了,composer和bower的作用非常相似。只不过composer是用来管理php资源库的。
composer的安装方法请见:http://www.moonlightgate.com/archives/233
composer安装好后,在cmd窗口中就可以直接使用composer命令了。

3.4.2 下载PHP依赖包

向上承接【B】

composer install

这时候,FastAdmin全部的代码就都齐全了。

你肯定会问,这好麻烦啊!直接使用安装包解压不就好了吗?何必搞得这么麻烦?这个问题现在我如何说你也是不会理解的,你需要做一段时间开发后才能明白这么做的意义!

3.5 一键创建数据库并导入数据

首先,确保你的php已经加入了环境变量,这样可以直接在cmd中使用php命令。
然后打开cmd,输入如下命令:

php think install -u 数据库用户名 -p 数据库密码

这样数据库就导入到指定的数据库里面了。

3.6 添加虚拟主机并绑定到项目的public目录

具体方法见:http://www.moonlightgate.com/archives/413

3.7 为了安全,安装完成后会在public目录生成随机后台入口,请通过随机后台入口登录管理后台

3.8 安装less

3.8.1 什么是less

用更加类似程序化的语法去编写CSS,比如可以在less里面创建变量,实现循环等等。
less文件本身不能被浏览器识别,需要被编译成(转换成)CSS后才能运行。

3.8.2 安装

这个安装教程里面没有提,但是为了后面编译less文件方便。可以在cmd里面使用以下命令进行安装:

cnpm install less -g

忘记MySQL数据库的root密码的重置方法(适合WAMP和普通MySQL环境)

1. 场景

1.1 安装WAMP环境

初始状态下,WAMP环境中MySQL的数据库密码是空。可以直接登陆MySQL数据库后修改。如果修改后的root密码忘记了,可以用本文方法进行密码重置。

1.2 普通MySQL环境

忘记了初始密码,需要重置初始密码,可以用本文方法进行密码重置。

2. 重置流程(安装了MySQL服务)

以下操作均在cmd命令行中完成。

2.1 停止MySQL服务

在cmd中通过以下命令停止MySQL服务

net stop mysql

2.2 以安全模式运行MySQL

mysqld –skip-grant-tables

2.3 免密登陆MySQL

保持2.2的cmd窗口不懂,另外打开一个cmd窗口后输入:

mysql -uroot -p

这时会提示输入密码,啥也不用输入直接回即可(我试了,随便输入任何内容回车都可以进入MySQL)。

2.4 重置root密码

2.4.1 MySQL5.7之前的版本
use mysql;
update user set password=password("111111") where user="root";
flush privileges;
exit
2.4.2 MySQL5.7及之后的版本
use mysql;
update user set authentication_string=password("111111") where user="root";
flush privileges;
exit

2.5 重置完成

这时候就可以使用111111这个密码登陆root用户了。

3. 重置流程(没有安装MySQL服务)

3.1 停止MySQL服务

ctrl+alt+delete进入任务管理器,然后结束掉mysqld进程。
file

3.2 以安全模式运行MySQL

在cmd中,先切换到mysql的bin目录,然后在bin目录里面运行:

mysqld –skip-grant-tables

3.3 免密登陆MySQL

保持2.2的cmd窗口不懂,另外打开一个cmd窗口后输入,同样的,在cmd中,先切换到mysql的bin目录,然后在bin目录里面运行:

mysql -uroot -p

这时会提示输入密码,啥也不用输入直接回即可(我试了,随便输入任何内容回车都可以进入MySQL)。

3.4 重置root密码

3.4.1 MySQL5.7之前的版本
use mysql;
update user set password=password("111111") where user="root";
flush privileges;
exit
3.4.2 MySQL5.7及之后的版本
use mysql;
update user set authentication_string=password("111111") where user="root";
flush privileges;
exit

3.5 重置完成

这时候就可以使用111111这个密码登陆root用户了。

4. 注意事项

如果使用wamp环境,有可能在重启MySQL的时候不成功。
解决方法是先关掉所有的cmd窗口,然后在任务管理器中停止掉mysqld进程。
再重启MySQL就可以了。

如何在Apache2.4中建立虚拟目录(虚拟主机)

1. Apache版本号

WAMP环境,Apache版本号:2.4.23
此方法同样适用于普通Apache环境。

2. 目标

在浏览器中输入http://localhost:8787的时候,访问F:\PHP_WORKPLACE\www\fastadmin\public文件夹。

3. 两个文件需要编辑

两个文件的目录如下:
如果非wamp环境,则在Apache安装目录:

  • apache安装目录\conf\extra\httpd-vhosts.conf
  • apache安装目录\conf\httpd.conf

如果是wamp环境,可以直接在wamp菜单下打开这两个文件。
file

3.1 httpd-vhosts.conf

增加如下代码:

<VirtualHost *:8787>
    ServerName localhost
    DocumentRoot F:/PHP_WORKPLACE/www/fastadmin/public
    <Directory  "F:/PHP_WORKPLACE/www/fastadmin/public/">
        Options +Indexes +Includes +FollowSymLinks +MultiViews
        AllowOverride All
        Require local
    </Directory>
</VirtualHost>

其中:
8787为访问时使用的端口号,可以任意指定。
DocumentRoot和Directory为绑定的目录地址。

3.2 httpd.conf

在文件中搜索Listen关键字,找到:
Listen 0.0.0.0:80
在这句的下面,输入:
Listen 0.0.0.0:8787
这里面的8787和httpd-vhosts.conf中的端口号对应。

4. 重启Apache

重启Apache,大功告成!

使用Grunt合并及压缩Javascript代码

1. Grunt安装

cnpm install -g grunt-cli 
cnpm install grunt --save-dev

2. 安装合并和压缩两个插件

cnpm install grunt-contrib-concat --save-dev
cnpm install grunt-contrib-uglify --save-dev

3. 根目录创建Gruntfile.js文件

文件内容如下:

module.exports = function(grunt){
    // 1. 初始化插件配置
    grunt.initConfig({
        //主要编码处
        concat: {
            options: { //可选项配置
                separator: ';'   //使用;连接合并
            },
            build: { //此名称任意
                src:  [
                        'src/com_0_adjustArray.js',
                        'src/com_1_globalVars.js',
                        'src/com_2_1_publicFunsSecret.js',
                        'src/com_2_2_publicFuns.js',
                        'src/com_3_subFuns.js',
                        'src/com_4_execFuns.js'
                    ], 
                dest: "dist/all.js" //输出的js文件
            }
        }
    });
    // 2. 加载插件任务
    grunt.loadNpmTasks('grunt-contrib-concat');
    // 3. 注册构建任务
    grunt.registerTask('default', ['concat']);
};

4. 目录结构

file

4. 执行

cmd中切换到目标目录后,直接运行grunt命令就可以了。
运行成功后,在dist文件夹下回出现合并的all.js文件及压缩后的all.min.js文件了。

5. 错误解决

运行grunt命令式弹出如下错误:

basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

解决方案很简答,将node_modules文件夹中的依赖全部删除,将package.json里面无关的依赖也删除,重新运行cnpm install命令安装依赖即可。
估计是之前在这个文件夹下测试了太多的程序,导致逻辑混乱吧。

Egg.js随学随记

1. Egg.js安装

1.1 安装Egg.js

cnpm install egg-init -g

采用全局安装,而非--save,原因有待后面分解。

1.2 在VS Code下安装Egg.js代码提示

file

2. 初始化Egg.js项目

egg-init project_name

如果这样输入命令,则在命令运行过程中会出现一个选项,让你选择所要创建的项目类型:
file
这里我选择新建一个simple项目,安装完成后,会弹出安装依赖和启动项目的提示命令:
file
这里要注意,创建项目的是新建一个文件夹。所以请合理的安排安装目录。
已经学到Egg.js了,各种cnpm的命令是什么意思应该很清楚了吧?
所以接下来安装依赖:

cnpm install

安装完成后,启动项目:

npm run dev

3. Egg.js目录约定

3.1 官方约定。

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (可选)
│   |   └── reset.css
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

3.2 MVC架构

我们的开发都在app目录下完成,Egg.js使用MVC架构,与文件夹对应关系如下:

├── app
|   ├── router.js(路由规则)
│   ├── controller(Controller)
│   |   └── home.js
│   ├── service (Model)
│   |   └── user.js
│   ├── middleware (中间件)
│   |   └── response_time.js
│   ├── public (静态资源)
│   |   └── reset.css
│   ├── view (View)
│   |   └── home.tpl
│   └── extend (扩展方法)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)

4. 一个mini的Egg.js项目

4.1 安装egg-view-ejs插件

cnpm install ejs --save
cnpm install egg-view-ejs --save

安装插件后需要配置两个文件:
文件1:/config/plugin.js

'use strict';

/** @type Egg.EggPlugin */
module.exports = {
  // had enabled by egg
  // static: {
  //   enable: true,
  // }
  ejs:{//增加部分
      enable: true,
      package: 'egg-view-ejs',
    }
};

文件2:/config/config.default.js

/* eslint valid-jsdoc: "off" */

'use strict';

/**
 * @param {Egg.EggAppInfo} appInfo app info
 */
module.exports = appInfo => {
  /**
   * built-in config
   * @type {Egg.EggAppConfig}
   **/
  const config = exports = {};

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1582681748898_3732';

  // add your middleware config here
  config.middleware = [];

  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
  };

  config.view = {//增加部分
    mapping: {
      '.html': 'ejs',
    },
  };

  return {
    ...config,
    ...userConfig,
  };
};

4.2 文件结构

├── app
|   ├── router.js
│   ├── controller
│   |   └── news.js
│   ├── service
│   |   └── news.js
│   ├── public
│       └── images
│              └── welcome.gif
│   ├── view
│       └── index.html
│       └── content.html
│       └── list.html

4.3 文件代码

4.3.1 router.js

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.news.index);
  router.get('/list', controller.news.list);
  router.get('/content/:newsid/:newscategory', controller.news.content);
};

4.3.2 service/news.js 模拟从数据库获取并返回数据

'use strict';

const Service = require("egg").Service;

class NewsService extends Service {
    async getNewsList(){
        //模拟从数据库模拟数据
        var dbData = ["A news about Tom","Jerry's news is cooler!","Popeye finally married Olive!"];
        return dbData;
    }
}

module.exports = NewsService;

4.3.3 controller/news.js 处理业务逻辑

'use strict';

const Controller = require('egg').Controller;

class NewsController extends Controller {
  async index() {
    const { ctx } = this;
    await ctx.render("index.html");
  }
  async list() {
    const { ctx } = this;
    var newsList = await ctx.service.news.getNewsList();
    await ctx.render("list.html",{
        newsList:newsList
    });
  }
  async content() {
    const { ctx } = this;
    var getInfo = ctx.query;
    var paraInfo = ctx.params;

    var name = getInfo.name;
    var level = getInfo.level;

    var newsId = paraInfo["newsid"];
    var newsCategory = paraInfo["newscategory"];

    await ctx.render("content.html",{
        name:name,
        level:level,
        newsid:newsId,
        newscategory:newsCategory
    });
  }
}

module.exports = NewsController;

4.3.4 view/三个文件

文件1:view/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h1>你好!欢迎进入迷你新闻系统!</h1>
        <h2>在Egg.js中,静态资源直接使用资源目录路径引用即可!比如下面的图片</h2>
        <img src="/public/images/welcome.gif" >
        <h3><a href="/list">点我进入新闻列表</a></h3>
    </body>
</html>

文件2:view/list.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h1>新闻列表</h1>
        <ul>
            <%for(let i=0;i<newsList.length;i++){%>
            <li><%=newsList[i]%></li>
            <%}%>
        </ul>

        <h3>
            <a href="/content/6/HotNews?name=Tom&level=3">
                这条新闻通过参数和动态路由传递数据,链接地址是:    /content/6/HotNews?name=Tom&level=3
            </a>
        </h3>
    </body>
</html>

文件3:view/content.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h3>Name is: <%=name%></h3>
        <h3>Level is: <%=level%></h3>
        <h3>News ID is: <%=newsid%></h3>
        <h3>News Category is: <%=newscategory%></h3>
    </body>
</html>

欲学乾坤大挪移,必先搞定九阳神功!

1. 大学为什么先学C语言?

大学计算机系大一课程中,C语言几乎是标配语言。一个学校是否以C语言作为启蒙计算机语言,很大程度上决定了这个学校的教学品质。
为什么这么说呢?因为C语言是最基础(从当代学习计算机编程角度)的计算机语言,把C语言学透,学扎实,后面学任何新语言都会很快。

这点上看是不是很像《倚天屠龙记》里面张无忌学习“九阳神功”和“乾坤大挪移”的桥段?

《倚天屠龙记》里面张无忌被困明教密道的时候,在小昭的帮助下偶得“乾坤大挪移”心法。张无忌在几个时辰里面学会了别人一生都不一定能学会的“乾坤大挪移”。

书中说的很明白,因为张无忌学习了几年的“九阳神功”,明白了很多学习高等武功的基础知识,再学习高等武功的时候便能融会贯通。

那些没有“九阳神功”基础的人,拿到“乾坤大挪移”后只能看的云里雾里,里面的武功招式看似相识,却又非常陌生,无法实际操练。

2.前端程序员先搞定ES6!

之所以提出这个话题,是因为最近学习前端技术的时候感触颇深。

这些年前端火的不得了,所以就决定自学Node.js,Vue.js,Koa2,Egg.js等等技术。由于自己有着很多年Javascript的基础,觉得直接上手这些技术应该不是难事儿。

最开始学了几天Node.js,觉得一知半解就开始学Vue.js,更加一知半解,然后进入Egg.js,基本上就蒙了。

学习的整体感觉就是,觉得里面的东西都是Javascript,觉得都似曾相识,但是就是觉得别扭,就是记不住。

然后重新梳理一遍,Egg.js基于Koa2,Koa2又基于Node.js,而这一切的一切,都是基于ES6。

于是花了点时间学习ES6,再学习Node.js等其它技术的时候,之前看的云里雾里的一下子豁然开朗。

3. 欲学乾坤大挪移,必先搞定九阳神功!

所以同学们,面对新技术,新知识,千万不要急。

在学习前,一定要先把学习顺序和学习方法搞清楚。

欲成就“乾坤大挪移”大业,必先搞定“九阳神功”!

Koa2随学随记

1. 什么是Koa,Koa2

  • Koa是Express的竞争品。Koa2是Koa的升级版本。
  • Koa和Koa2与Express一样,都是基于Node.js开发的Web框架。
  • 在koa中,一切的流程都是中间件,数据流向遵循洋葱模型,先入后出,是按照类似堆栈的方式组织和执行的,koa-compose是理解koa中间件的关键,在koa中间件中会深入分析。
  • koa2与koa1的最大区别是koa2实现异步是通过async/awaite,koa1实现异步是通过generator/yield,而express实现异步是通过回调函数的方式。
  • koa2与express 提供的API大致相同,express是大而全,内置了大多数的中间件,更让人省心,koa2不绑定任何的框架,干净简洁,小而精,更容易实现定制化,扩展性好。
  • express是没有提供ctx来提供上下流服务,需要更多的手动处理,express本身是不支持洋葱模型的数据流入流出能力的,需要引入其他的插件。
    以上内容参考:https://www.jianshu.com/p/a518c3d9c56d

2. Koa的安装和第一个Koa程序。

2.1 Koa安装

cmd进入工作目录,运行如下命令

cpmn install koa--save

2.2 第一个Koa程序

在工作目录新建app.js,并输入以下代码:

const koa = require("koa");
const app = new koa();

app.use(
    async (ctx) => {
        ctx.body="Hello Koa!"
    }
);

app.listen(3000);

在cmd中运行:

node app.js

浏览器中输入 localhost:3000 便可以访问使用koa建立的web服务器了。

3. Koa路由

3.1 Koa路由功能安装。

Koa的路由功能不是标配(这点和Express不同),需要在安装Koa的基础上安装路由模块Koa-router。
cmd切换到工作目录后使用以下命令安装:

cnpm install koa-router --save

3.2 第一个路由示例

const Koa = require("koa");
const Router = require("koa-router");

var app = new Koa();
var router = new Router();

//逐条指定方式
router.get("/",async(ctx)=>{ //ctx包含request和require等信息.
    ctx.body = "这里是首页";
});
router.get("/news",async(ctx)=>{
    ctx.body = "这里是新闻页面";
});

//连续简写方式
router.get("/list",async(ctx)=>{
    ctx.body = "这里是列表页面";
}).get("/details",async(ctx)=>{
    ctx.body = "这里是细节页面";
});

app.use(router.routes());//使用中间件:启动路由
app.use(router.allowedMethods());//使用中间件:设置响应头,比如页面出错的时候返回404

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

3.3 动态路由

其实更好理解的方式就是通过路由(URL)传参。

const Koa = require("koa");
const Router = require("koa-router");

var app = new Koa();
var router = new Router();

router.get("/product/:name/:brand/:price",(ctx)=>{
    console.log(ctx.params);
    ctx.body="产品页面!";
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

访问 http://localhost:3000/page?name=Tom&gender=male ,cmd中结果如下:
file

4. Koa中间件

中间件的概念类似于其它语言中的过滤器。
Koa中间件的运行流程采用的洋葱模式。

const Koa = require("koa");
const Router = require("koa-router");

var app = new Koa();
var router = new Router();

//应用级中间件,app.use("可选路由",函数)
//如果指定路由,则只对目标路由有效,如果不指定,则对所有路由有效.
app.use(async (ctx,next)=>{
    ctx.body="This is the middleware";
    await next();//如果没有next()指令,则其它的代码不会再执行.
});
router.get("/",async(ctx)=>{
    ctx.body += "This is the index page!";//所以呢,为了后期使用中间件更方便,即便初次指定ctx.body,也建议使用+=这种赋值方法.
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

5. ejs模板引擎的使用

5.1 安装koa-views和ejs

cnpm install koa-views --save
cnpm install ejs --save

5.2 第一个ejs示例

本示例共有三个文件。
第1个文件:/根目录/koa-views.js,代码如下:

const Koa = require("koa");
const Router = require("koa-router");
const Views = require("koa-views");

var app = new Koa();
var router = new Router();

app.use(Views("./views",{ //   ./views 指明模板文件夹位置
    //extension:"ejs"    //方案一:识别.ejs文件.
    map:{html:'ejs'}   //方案二:将.html视为.ejs文件,可以在html文件中撰写ejs代码.
}));

app.use(async(ctx,next)=>{   //配置公共变量信息
    ctx.state.userinfo = "Tom";
    await next();
});

router.get("/fejs",async(ctx)=>{
    let title = "hello EJS";
    let arr = ["111","222","333"];
    await ctx.render("firstEJS",{ //await必须加
        title:title,
        list:arr
    });
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

第2个文件:/根目录/views/firstEJS.html,代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <%- include('header.html') -%> <!--注意:最新版本的EJS需要用这种语法引入文件-->
        <h2><%=title%></h2>
        这里是EJS文件
        <ul>
            <%for(var i=0;i<list.length;i++){%>
                <li><%=list[i]%></li>
            <%}%>
        </ul>
        <%=userinfo%>
    </body>
</html>

第3个文件:/根目录/views/header.html,代码如下:

<h1>这里是头部内容!</h1>

命令行运行:

node koa-view.js

浏览器中打开 http://localhost:3000 显示结果如下:
file

6. 获取get和post数据。

6.1 获取get数据

通过ctx.query

const Koa = require("koa");
const Router = require("koa-router");

var app = new Koa();
var router = new Router();

router.get("/page",async(ctx)=>{
    console.log(ctx.query);
    console.log(ctx.querystring);
    ctx.body = "hello page!";
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

访问 http://localhost:3000/page?name=Tom&gender=male ,cmd中结果如下:
file

6.2 获取post数据。

6.2.1 安装koa-bodyparser

cnpm install koa-bodyparser --save

6.2.2 获取post数据示例

本示例有两个文件:
第1个文件:/根目录/postdata.js

const Koa = require("koa");
const Router = require("koa-router");
const Views = require("koa-views");
var bodyParser = require("koa-bodyparser");

var app = new Koa();
var router = new Router();

app.use(Views("./views",{ //   ./views 指明模板文件夹位置
    //extension:"ejs"    //方案一:识别.ejs文件.
    map:{html:'ejs'}   //方案二:将.html视为.ejs文件,可以在html文件中撰写ejs代码.
}));

router.get("/",async(ctx,next)=>{
    await ctx.render("index");
});

app.use(bodyParser());
//获取post数据
router.post("/doAdd",async(ctx)=>{
    ctx.body = ctx.request.body;
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

第2个文件:/根目录/views/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <form action="/doAdd" method="post">
            <input type="text" name="username" />
            <input type="password" name="password"/>
            <input type="submit" value="提交"/>
        </form>
    </body>
</html>

在浏览器中访问 http://localhost:3000 随意填写数据后提交,结果如下:
file

7. koa-static静态资源中间件

7.1 安装koa-static

cnpm install koa-static --save

7.2 第一个koa-static示例

与express的static非常相似。
本示例有三个示例文件。
文件1:/根目录/view/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="stylesheet" type="text/css" href="./css/style.css"/>
    </head>
    <body>
        你好!
    </body>
</html>

文件2:/根目录/static/css/style.css

body{
    font-size:50px;
}

文件3:/根目录/index.js

const Koa = require("koa");
const Router = require("koa-router");
const Views = require("koa-views");
const Static = require("koa-static");

var app = new Koa();
var router = new Router();

app.use(Views("./views",{ //   ./views 指明模板文件夹位置
    //extension:"ejs"    //方案一:识别.ejs文件.
    map:{html:'ejs'}   //方案二:将.html视为.ejs文件,可以在html文件中撰写ejs代码.
}));

router.get("/",async(ctx,next)=>{
    await ctx.render("index");
});

app.use(Static("static"));

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

8. art-template模板引擎的使用

art-template是ejs的竞争品,用法和ejs非常类似。只不过速度比ejs要快。

8.1 art-template安装

cnpm install art-template --save
cnpm install koa-art-template --save

8.2 art-tempalte示例

本示例有三个文件:
文件1:/根目录/art_template.js

const Koa = require("koa");
const Router = require("koa-router");
const render = require("koa-art-template");

var app = new Koa();
var router = new Router();

render(app,{
    root:"views",//目录
    extname:".html",//文件名后缀,可以任意指定
    debug:process.env.NODE_ENV !== "production"//是否开启调试模式
});

router.get("/",async(ctx)=>{
    let list = {
        name:"Tom"
    };
    await ctx.render("index",{
        list:list
    });
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000,()=>{
    console.log("http://localhost:3000 is ready!");
});

文件2:/根目录/views/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        {{include "./header.html"}}
        <br/> 
        <% include("./header.html") %>
        <br/>
        你好!
        <br/>
        {{list.name}}           <!--两种模板语法都可以-->
        <br/>
        <%=list.name%>            <!--两种模板语法都可以-->
    </body>
</html>

文件2:/根目录/views/header.html

<h1>这里是头部内容!</h1>

Javascript的异步机制

0. 异步入坑。

讨论这个问题一定是在入坑Node.js后,而且很有可能是在入坑Express和Koa后。在ES5和ES6中,与异步相关的机制主要涉及以下几个概念:

  • 回调函数(ES5)
  • Promise + then(ES6)
  • Generator + next(ES6)
  • async + await(ES6)

1. 回调函数

Javascript异步操作最基本的解决方案。
由于name的定义是异步的,存在1秒的时延,所以name变量只有通过回调函数才能获取到。

function getData(callback){
    setTimeout(()=>{
        var name = "Tom";
        callback(name);
    },1000);
}
getData((data)=>{
    console.log(`Name is ${data}`);
});

2. Promise + then

ES6异步的另一种解决方案。和回调函数的语法和逻辑最为相像。

function getData(resolve,reject){
    setTimeout(()=>{
        try{
            var name = "Tom";
            resolve(name);
        }catch(e){
            reject(e);
        }
    },1000);
}
var p = new Promise(getData);
p.then((data)=>{
    console.log(`My name is ${data}!`);
});

上面的代码也可以合并简化一下:

var p = new Promise(function(resolve,reject){
    setTimeout(function(){
        try{
            var name = "Tom";
            resolve(name);
        }catch(e){
            reject(e);
        }
    },1000);
});
p.then((data)=>{
    console.log(`My name is ${data}!`);
});

3. Generator + next

3.1 基本概念。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

上面代码一共调用了四次next方法。

第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。

第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。

第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.2 异步

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器)g。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,上例是执行到x + 2为止。

换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

4. async + await

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。(此部分参考:https://es6.ruanyifeng.com)

移动端开发的一些常用知识

1. 字体的px,em和rem

1.1 px

px是固定的像素,一旦设置了就无法因为适应页面大小而改变。

1.2 em

  • 子元素字体大小的em是相对于父元素字体大小
  • 元素的width/height/padding/margin用em的话是相对于该元素的font-size

1.3 rem

rem中的r代表的是是root。
rem是全部的长度都相对于根元素,根元素是谁?<html>元素。
通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

1.4 总结

在做项目的时候用什么单位长度取决于具体的需求,但一般是这样的:

  • 像素(px):用于元素的边框或定位。
  • em/rem:用于做响应式页面,不过我更倾向于rem,因为em不同元素的参照物不一样(都是该元素父元素),所以在计算的时候不方便,相比之下rem就只有一个参照物(html元素),这样计算起来更清晰。

2. 分辨率,PPI,DPI,DPR的概念解释

2.1 分辨率

是指设备屏幕宽度上和高度上最多能显示的物理像素点个数。

2.2 PPI

PPI:Pixel Per Inch。屏幕像素密度,即每英寸(1英寸=2.54厘米)聚集的像素点个数,这里的一英寸是对角线长度。

2.3 DPI

DPI:Dots Per Inch。每英寸像素点,印刷行业标准。衡量打印机精度以及图片精度的标准。

2.4 DPR

DPR:Device Pixel Ratio。是默认缩放为100%的情况下,设备物理像素和CSS像素的比值。
举个例子:
这里先无限diss B站中的某个傻X老师,乱讲一通,害得我思考了好久。
iPhone6的CSS的物理像素是:750x1334。
iPhone6默认的DPR=2,那么其默认的css逻辑像素就375x667。
这样的结果是什么呢?结果就是,如果你在代码中写了

<div style="width=375px;height:667px"></div>

这个div就会充满iPhone的屏幕。相当于css中的1px占用了手机的2个像素。

2.5 应用

2.5.1 有了以上的概念,我们便可以通过动态viewport设置页面,使css像素对应设备屏幕物理像素。
<script type="text/javascript">
        var scale = 1/window.devicePixelRatio;
        document.write(`
            <meta name="viewport" content="initial-scale=${scale},minimum-scale=${scale},maximum-scale=${scale},user-scalable=no" />
        `);
</script>
2.5.2 还可以模仿微信对屏幕的丈量标准,将屏幕宽度分为750份。
<script type="text/javascript">
    var unit = window.innerWidth / 750; //将屏幕分为750个单位
    document.write(`
        <style>
            html{
                font-size:$(100*unit)px; /*根元素字体大小是100个单位*/
            }
            .zhengWen{
                font-size:0.05rem; /*正文字体大小是5个单位*/
            }
        </style>
    `);
</script>

Vue.js随学随记

1. v-bind和v-model理解

1.1 v-bind

v-bind代表元素的某个属性交给vue处理,实现元素属性和vue的绑定,v-bind="表达式",引号里面的本质是表达式。但是这种绑定是单向的。比如下面的代码,当你在网页中修改input中值的时候,上面的message内容不会随着修改。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-bind:value="message"/>
            </div>
        </div>

        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

如果想实现双向绑定,还需要向下方一样为标签添加v-on指令,这样在修改input里面内容的时候,上面message的内容也会相应的修改。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-on:input="message = $event.target.value" v-bind:value="message"/>
            </div>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

1.2 v-model

v-model实际上是v-bind:和v-on:的语法糖,实现了简易的元素属性双向绑定。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-model="message"/>
                <!--上方代码等同于下方代码
                <input type="text" v-on:input="message = $event.target.value" v-bind:value="message"/>
                -->
            </div>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

2. v-on:click 事件调用函数的问题。

2.1 方法一:直接调用函数名

<button v-on:click="change">改变一下</button>

这种情况下,函数被调用时,自动传递$event参数。

2.2 方法二:调用函数名()

这种情况下,函数被调用时,不会自动传递$event参数,如果需要,则必须人工指定为函数名($event)。也就是说下面的代码等同于上面的代码:

<button v-on:click="change($event)">改变一下</button>

3. v-if和v-show的区别。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <button type="button" v-if="isShowButton">使用v-if判断</button>
            <button type="button" v-show="isShowButton">使用v-show判断</button>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    isShowButton:false
                }
            });
        </script>
    </body>
</html>

运行结果如下:
file

  • 对于v-if,如果=后面的表达式是false,则整个标签不会显示。
  • 对于v-show,如果=后面的表达式是false,则会以display:none的形式隐藏标签。

4. v-for对数组和对象的使用方法。

4.1 示例代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <table border="1" cellspacing="0" cellpadding="0">
                <tr><th>姓名</th><th>性别</th><th>分数</th></tr>
                <tr v-for="student,arrayIndex in students">
                    <td v-for="value,key,objectIndex in student">{{arrayIndex}} - {{objectIndex}} - {{key}} - {{value}}</td>
                </tr>
            </table>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    students:[
                        {name:"张三",gender:"男",score:"100"},
                        {name:"李四",gender:"女",score:"88"},
                        {name:"王五",gender:"男",score:"99"}
                    ]
                }
            });
        </script>
    </body>
</html>

运行结果如下:
file

4.2 对于数组

<tr v-for="student,arrayIndex in students">
</tr>

遍历students数组的每一个元素。

4.3 对于对象

<td v-for="value,key,objectIndex in student">{{arrayIndex}} - {{objectIndex}} - {{key}} - {{value}}</td>

遍历student对象的每一个属性。

5. Vue-cli 3.0 的使用

5.0 理解Vue-cli的关键点

vue-cli是将Node.js和webpack封装进了vue-cli内部。

5.1 安装Vue-cli 3.0

cnpm install -g @vue/cli

需要注意的是,如果之前安装过Vue-cli的2.X版本,需要先卸载之前的版本再安装。卸载方法在安装Vue-cli 3.0的时候有提示,这里不赘述。

5.2 创建项目

安装完Vue-cli 3.0工具后,我们在命令行里面就可以直接使用vue命令了,下面的命令用于创建一个Vue.js的项目框架:

vue create 项目名

这里面有几个注意点:

  1. vue create 项目名命令只对Vue-cli 3.0及以后版本有效,2.X版本无法使用vue create命令
  2. 运行这个命令后,会自动在cmd当前目录下创建【项目名】文件夹,然后在里面生成Vue.js的项目框架。
  3. 这里要特别解释下“脚手架”这个词。初次学Vue.js的时候,对“脚手架”工具特别不理解,不明白啥叫脚手架。说白了,“脚手架”就是辅助工具。Vue-cli的作用就是辅助进行大型的Vue.js项目开发。如果只是在项目中一般性的使用Vue.js,在网页中使用script引入Vue.js文件就够了,不是必须使用Vue-cli脚手架工具。
  4. 项目创建好后,项目的目录结构如下:
    file
    从上面的截图能看出来,其实vue create命令创建的是个node.js项目。其目的和原因也很简单:通过Node.js模拟服务器运行及热更新环境,这样可以让开发过程更加简单、高效。所以,如果要使用Vue-cli工具,前提是机器中必须提前安装了Node.js(如果不使用Vue-cli工具而是只用Vue.js,可以没有Node.js环境)。

5.3 构建项目

既然vue create项目创建的是Node.js项目,那么当时用npm run命令的时候,就可以直接调用package.json里面<script>标签下的三个命令。

5.1.1 serve命令:开发环境构建
npm run serve

这一步最主要的操作是通过Node.js启动一个Web服务器,这样可以直接在本地模拟服务器环境。

5.1.2 build命令:生产环境构建
npm run build

运行这个命令后,会在项目根目录生成一个【dist】文件夹,这里面的文件是经过编译、打包、压缩后,最终用于部署到服务器上的代码。
实际上,这个操作是webpack做的,vue-cli只是把webpack封装进去了。。

5.1.3 lint命令:代码检查工具
npm run lint

6 vue-router路由库使用

6.1 关于SPA

vue-router路由一般使用在SPA(Single Page Application)单页面程序上。SPA的简单的来说就是无论程序有多复杂,所有程序代码都写在一个页面上。
当然,在开发阶段,SPA项目开始分模块开发的,开发完毕后会通过构建工具将各类模块组成一个单页面程序。
SPA的有点有很多,比如:一次加载后,页面无需再跳转和下载新内容,页面速度快;再比如:有效降低网页被复制和爬取的风险。缺点也很明显,搜索引擎索引起来不是很好。不过SPA程序一开始就不是为搜索引擎而设计的。

6.2 vue-router

vue-router是vue.js的官方路由工具。第三方路由如Page.js、Director也很好用,与vue.js的集成也比较容易。

6.3 vue-router的安装

cnpm install vue-router --save

或者直接下载vue-router.js导入到项目中。

6.4 vue-router的使用

6.4.1 HTML代码
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>
6.4.2 Javascript代码
// 0. 如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写)相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')

// 现在,应用已经启动了!

当然,上面代码也可以稍微简化一下:

const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

const router = new VueRouter({
  routes = [
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]
})

const app = new Vue({
  router
}).$mount('#app')