0%

使用browser-sync 来用作服务器,实现实时刷新

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{% highlight bash %}
{
"name": "test",
"version": "1.0.0",
"description": "test",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "weibin",
"license": "ISC",
"devDependencies": {
"gulp": "^3.9.1",
"browser-sync": "^2.17.3",
"gulp-connect": "^5.0.0",
"gulp-file-include": "^1.0.0",
"gulp-imagemin": "^3.0.3",
"gulp-less": "^3.1.0",
"gulp-notify": "^2.2.0",
"gulp-plumber": "^1.1.0",
"gulp-watch": "^4.3.10"
}
}

gulpfile.js

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
var gulp = require('gulp'); //本地安装gulp所用到的地方
var fileinclude = require('gulp-file-include'); //包含HTML
var connect = require('gulp-connect'); //本地服务
var imagemin = require('gulp-imagemin'); //图片压缩
var watch = require('gulp-watch'); //监听
var less = require('gulp-less');
var notify = require('gulp-notify'); //处理LESS错误
var plumber = require('gulp-plumber'); //处理LESS错误
var browserSync = require('browser-sync');
var reload = browserSync.reload;

/*//本地服务
gulp.task('webserver', function() {
connect.server({
port: 8080, //修改端口
livereload: true //添加实时刷新,需要watch相应的css.js,html文件
});
});*/

//include html并刷新服务器
gulp.task('fileinclude', function(done) {
gulp.src(['src/html/*.html'])
.pipe(fileinclude({
prefix: '@@',
basepath: '@file'
}))
.pipe(gulp.dest('dist/html'))

.on('end', done)
.pipe(reload({ stream: true }))
});

//压缩图片
gulp.task('imagemin', function() {
gulp.src('src/images/*')
.pipe(imagemin())
.pipe(gulp.dest('dist/images')).pipe(reload({ stream: true }));
});

//打包CSS进入到dist目录
gulp.task('csstodist', function() {
gulp.src('src/css/*.css')
.pipe(gulp.dest('dist/css')).pipe(reload({ stream: true }));
});

//打包JS到dist目录
gulp.task('jstodist', function() {
gulp.src('src/js/*')
.pipe(gulp.dest('dist/js')).pipe(reload({ stream: true }));
});

gulp.task('testLess', function () {
gulp.src('src/css/*.less')
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(less())
.pipe(gulp.dest('dist/css')).pipe(reload({ stream: true }));
});

// 监视文件改动并重新载入
gulp.task('serve', function() {
browserSync({
port: 8080,
server: {
baseDir: 'dist/'
}
});
});

//监听所有HTML JS CSS改动
gulp.task('watch', function () {
// gulp.watch(['src/css/*','src/css/*.less','src/html/*.html','src/js/*','src/images/*'], ['testLess','jstodist','csstodist','fileinclude','imagemin']);
gulp.watch(['src/**/*.css'], ['csstodist']);
gulp.watch(['src/css/*.less'], ['testLess']);
gulp.watch(['src/html/**/*.html'], ['fileinclude']);
gulp.watch(['src/js/*'], ['jstodist']);
gulp.watch(['src/images/*'], ['imagemin']);
});




gulp.task('default',['testLess','fileinclude','serve','imagemin','csstodist','jstodist','watch']);

运行gulp完毕后 打开 localhost:8080/html

前言

利用vue-cli 快速构建VUE项目

安装步骤

1
2
3
4
5
$ npm install -g vue-cli

$ vue init webpack 《你的项目名称》
//安装1.0版本
vue init webpack#1.0 sell

配置一些名字 描述什么的 就可以了

然后 cd 你的项目 进入项目目录

1
2
npm install
npm run dev

运行完毕后会启动服务 默认监听localhost:8080

关于目录结构中的.eslintrc.js

这个是检查ES6语法的,双击打开
可以看到 github上的规则地址
并且可以修改,找到规则名字 变成0就可以了。

1
2
3
4
5
6
7
8
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}

模拟假数据 data.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var data = [
{
name:'ipone6',
price:5499,
id:1,
count:1
},
{
name:'ipad',
price:3299,
id:2,
count:1
},
{
name:'iMac',
price:15999,
id:3,
count:1
}
]
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
99
100
101
102
103
104
105
<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{margin: 0; padding:0;-moz-user-select: none; /*火狐*/
-webkit-user-select: none; /*webkit浏览器*/-ms-user-select: none; /*IE10*/-khtml-user-select: none; /*早期浏览器*/
user-select: none;}
.products .item span{border: 1px solid #ccc; width: 100px; display: inline-block; text-align: center; height:40px; line-height:40px;}
.products .item span em{width: 20px;height: 20px; text-align: center; border: 1px solid #ccc;
background-color:orangered; color: #fff; display: inline-block;line-height: 20px; cursor: pointer;}
.products .item span i{width: 50px; line-height: 30px; text-align: center; background: orangered; color: #fff;
height:30px; font-size: 14px; font-style: normal; display: inline-block}
.info{margin-top: 50px}
.products .item span em.off{background-color: #ccc;}
</style>
<script src="vue.js"></script>
<script src="data.js"></script>
</head>
<body>
<div id="app">
<!--用template 判断购物车是不是为空 做不同的显示-->
<template v-if="data.length">
<div class="products">
<div class="item">
<span>商品名称</span>
<span>购买单价</span>
<span>商品数量</span>
<span>操作</span>
</div>
<div class="item" v-for="item in data">
<span>{{item.name}}</span>
<span>{{item.price | currency '¥' 0}}</span>
<span>
<em :class="{off:item.count == 1}" @click="reduce($index)">-</em>
{{item.count}}
<em @click="add($index)">+</em>
</span>
<span>
<i v-on:click="remove($index)">移除</i>
</span>
</div>
</div>
<div class="info">
<label for="">收件人:</label>
<input type="text" v-model="name">
<label for="">收货地址:</label>
<input type="text" v-model="address">
</div>
<div class="result">
<h3>清单</h3>
<!--内置过滤器, 第三个参数代表保留几位-->
商品总价:{{ price | currency '¥' 0}}<br>
收件人:{{name}}<br>
收货地址:{{address}}
</div>
</template>
<template v-else>
购物车为空
</template>

</div>
<script>
var vm = new Vue({
el:"#app",
data:{
data:data,
name:"",
address:""
},
computed:{
//自动计算所有单价*数量
price:function(){
var price = 0;
for(var i=0; i<this.data.length; i++)
{
price += this.data[i].price* this.data[i].count
}
return price
}
},
methods:{
reduce:function(index){
if(this.data[index].count == 1)
{
return false
}
this.data[index].count--
},
add:function(index){
this.data[index].count++
},
remove:function(index)
{
//spilce 方法 第一个参数是要删除的起始位置,第二个参数是要删除的个数
this.data.splice(index,1)

// this.data.$remove(item) //如果上边传进来这个item 可以用这种VUE自带的方法直接删除这个ITEM;
}
}
})
</script>
</body>
</html>

目录:

1,何为Hybrid App
2, APICLOUD控制台简介
3,开发工具
4,APICLOUD提供的前端开发框架,端API介绍及相应的代码举例

何为Hybrid App

首先介绍下Hybrid App
讲述Hybrid App,要先了解Native App和Web App的定义。Native App是指针对不同的手机操作系统要采用不同的语言和框架进行开发,例如Java(Android), Objective-C(iOS)等;Web App即是 HTML5 APP 框架开发模式,使用HTML5,CSS3以及JavaScript以及服务器端语言来完成开发,WebApp具有跨平台的优势。根据定义显而易见得出两种开发模式的优劣势,Native App可以利用系统的所有特性,因此做出杰出的性能,然而每次功能升级都必须重新打包、审核再上架,平均浪费近两周的时间。而反观Web App就没有这样的问题,其缺点在于Web语言无法访问很多系统特性,性能不高。

鉴于二者各有的优劣势,顺理成章的衍生出了介于中间的开发模式Hybrid。其特点是在原生应用中嵌入一个浏览器组件,然后通过某种方式,让原生代码和网页能够双向通讯,结果就是可以在需要原生功能的时候使用原生功能,而适合放在网页端的部分就放在服务器上。某种程度上利用到了两者的优势。另一个优势就是,由于网页技术在 iOS 和 Android 上是一样的,所以网页的这部分也就天然可以跨平台了。

我今天和大家分享的APPCLOUD就是一套Hybrid APP开发的平台,使用APICLOUD开发的APP 和 原生开发的APP 区别很小,这是因为除了使用HTML CSS JS这些WEB开发技术外,APICLOUD为我们提供了很多原生的模块,供我们在APP中调用,这种原生+WEB的混合模式降低了开发成本,开发门槛,并且可以满足用户的需要。

APICLOUD控制台简介

好了,基本概念到此为止,我们可以直接访问APICLOUD的主页http://www.apicloud.com/,我们要开发一款APP应用,首先要先注册一个账号,然后进入开发控制台,在里面创建我们的应用,----(如图)-----
这里面的东西很多,今天主要和大家介绍下端开发这部分的内容。

端开发,分为以下几部分:
端设置:设置一些APP的图标和启动页面
证书:上传安卓和IOS证书的地方,其中安卓证书可以直接创建,IOS证书需要使用苹果的开发者账号创建,然后上传
代码:提供一些代码更新的日志,其中的代码分支信息是 SVN云端地址,我们可以方便的使用小乌龟等SVN工具随时随地进行代码更新上传
模块:提供官方或者第三方开发者开发的模块,可以将其在我们的HTML代码中进行调用
云编译: 将我们的代码进行云端打包 ,生成多个平台的正式版或测试版的安装包,也可以进行一些加密,压缩的操作,编译完成后就可以把APP上传到相应的应用市场

大家可以看到,端开发这些个内容,其实就是我们开发APP的一套流程,就是把我们的前端代码编译成可以同时安装在IOS和安卓平台上的安装包。

开发工具

工欲善其事必先利其器,使用APICLOUD,那么我们的开发工具应该选用什么呢?
目前我们前端开发主流工具 sublime webstrom 这些APICLOUD都有插件支持,这里建议大家使用他推荐的开发工具APICloud Studio,可以无须再安装插件并且可以在IDE中直接更新,提交代码,更加方便的进行开发调试。我本人是使用sublime通过PackageControl安装的插件,我以sublime举例,按照教程安装好插件后,我们可以新建一个APICOUD项目,项目的结构如图所示—-(如图)—–,我们前端在要按照他提供的目录结构进行开发,

大家看他的根目录下有一个config.xml文件,它的作用是配置一些APP的默认行为和运行参数,
比如 是否全屏显示

是否开启调试模式

状态栏和页面是否重合(沉浸式效果)等等

简略说了下配置文件,我们回到开发工具上,
右键项目根目录 就会有如下菜单:—-(如图)—– ,其中我们最常用的是WIFI真机同步, IOS/安卓真机同步,我们在本地开发的过程中可以随时使用手机调试代码, 比如使用IOS/安卓真机同步,我们要做的就是把USB线连接手机和电脑(手机要开启USB调试权限),然后在项目上点击IOS/安卓真机同步,他会自动打包到手机上,我们就可以看到效果了。
如果大家觉得用手机不方便,也可以在电脑上下载安卓模拟器来进行调试。这个看下教程简单操作下就行了,我就不在这里说了。

APICLOUD提供的前端开发框架,端API介绍及相应的代码举例

下面给大家举例一些代码

首先 APICLOUD自己提供一套前端框架API —-(如图)—–

一眼看去,前端同学可能会觉得很熟悉,这怎么和JQUERY那么像嘛,其实这个东西的作用确实类似于JQ,我们在开发移动应用的时候,引入JQ这种文件比较大的库可能会对运行效率,性能有些影响,所以它就为我们封装好了一些常用的DOM操作,工具函数(string ,Format)还封装了一些比如localStroge这些方法,AJAX方法。这个文件的源代码位于script/api.js下,通过阅读源码,我们可以清晰的看到,他创建了一个空对象u,并把所有创建的方法挂到这个对象u上,最后window.$api = u; 这样命名空间为 $api ,所有方法可如此调用,比如:
$api.addCls(el, ‘newclass’); el是你要绑定的DOM元素,对比 JQUERY 方法 $(dom).addClass(‘newclass’);
再比如事件绑定方法
$api.addEvt(element, ‘click’, function(){
//do something
});
这些其实JQ十分类似,这就为我们在开发APICLOUD应用时候操作DOM,处理事件提供了除了原生JS,JQ/zepto等库外的一种选择。

接下来,我们来看看端API部分的API对象,那么这是个什么东西?我们看看官方文档上的概述:
api 对象是您入门 APICloud 必须了解和熟练掌握的一个基础对象。api 对象提供了构建应用程序所需要的一些基本的方法[Method],如窗口操作、相册和网络数据访问等;以及一些常见的属性[Attrbute],如屏幕宽度(screenWidth),系统类型(systemType)等;还有一些常用事件[Event],如电量低(batterylow)事件、应用进入后台(pause)事件。api 对象不需要 require 引用,可以直接在 js 中使用。

这个描述还是挺清楚的,我就不用过多解释了,提醒大家注意两点 ,
1,端API对象调用方法如:var appId = api.appId;和刚才介绍过的前端框架使用$api有所区别
2,端API对象调用时候要写在 apiready = function() {}中,这个的作用是为了等待端api初始化成功再执行的方法。

下面给大家介绍下我们常用的内置端API对象

APICLOUD为我们提供了很多内置对象
比如一个用的最多的打开新窗口的操作

1
2
3
4
5
6
7
api.openWin({
name: 'page1',
url: './page1.html',
pageParam: {
name: 'test'
}
});

以上代码调用了 端API中封装好的方法,name指这个窗口的名字。url就是页面地址,可以为本地文件路径,也可以为远程地址,pageParam是页面传参,新页面中可以通过 api.pageParam.xxx 获取

再比如说一个下拉刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
api.setRefreshHeaderInfo({
visible: true,
loadingImg: '../image/refresh.png',
bgColor: '#f4f4f4',
textColor: '#666',
textDown: '下拉刷新',
textUp: '松开刷新',
showTime: false

}, function(ret, err) {
//查询数据库
//判断是否有新数据
//更新新数据

api.refreshHeaderLoadDone();
});

上面那些属性大家一看就懂,我们要做的就是在回掉函数中加入我们的代码。

那么如果我们要调用非内置的功能怎么办呢,比如我要调用个类似于原生功能的日历

首先,我们还是进入控制台,点击左侧的模块,选择模块库—-(如图)—– , 大家可以看到 里面有很多官方或者个人提供的模块,我们选定好要用到的模块将其添加至项目,
然后在代码中调用

1
2
3
4
5
6
7
8
9
10
var calendar = api.require('calendar');
calendar.open({
x: 100,
y: 100,
width: 300,
height: 300,
specialDate: ['2014-05-01', '2014-05-11', '2014-05-20', '2014-05-25', '2014-05-31']
}, function(ret, err) {
var date = ret.date;
});

大家可以看到 通过简单的require加载,open调用就可以使用了。如果团队中有IOS或者安卓原生开发者,在现有模块无法满足业务需要的时候, 也可以自己开发并上传模块,这个是可以交易变现的。

最后再说下 打包上传,项目开发一段落后我们要上传代码,这时候我们可以在项目上右键 压缩widget包 或本地打包 提交到APICLOUD云端 ,再经过编译成正
式包(widget包是zip包,本地打包是打包成APK文件) , 通过控制台 云修复功能可以上传ZIP包, 我们再打开应用的时候就会有提示更新了,非常的方便。


兼容性

通过APICloud开发的应用,ios平台最低支持至ios5.0版本,Android平台最低支持至Android2.3.0版本

安全性

在客户端代码保密方面,APICloud端有一套“运行时解密”机制,在云编译阶段,将开发者的代码进行加密,同时只有在应用运行时才解密代码,整个过程在内存中进行,应用退出即消失,不会留下解密缓存而导致代码泄露。

数据传输安全方面,APICloud支持https标准请求,支持自定义https证书。考虑到https传输效率较低,APICloud提供端到云的数据加/解密整体解决方案,在http级别下传输加密数据。

对比其他开发平台 APPCAN cordova dcloud

http://blog.csdn.net/kenkao/article/details/50678269

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<input type='checkbox' class='input-checkbox' v-on:click='checkedAll($event)'>全选
<button @click="checkBtn($event)">全选</button>
<template v-for='checkb in checkboxData'>
<input type='checkbox' name='checkboxinput' class='input-checkbox' v-model='checkboxModel' value='{{checkb.id}}'>{{checkb.value}}
</template>
</div>


<div id="app1">
<input type="checkbox" value="Jack123" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames | json }}</span>
</div>
<script type="text/javascript">
var vm = new Vue({
el:"#app",
data:{
checkboxData:[{
id:'1',
value:'苹果'
},{
id:'2',
value:'荔枝'
},{
id:'3',
value:'香蕉'
},{
id:'4',
value:'火龙果'
}],
checkboxModel:['3','1','4'],
checked:""
},
methods:{
checkedAll: function(event) {
//event.currentTarget 取得当前的DOM元素
var ischeck = event.currentTarget.checked
var _this = this;
if (!ischeck) {//实现反选
console.log(ischeck)
_this.checkboxModel = [];
// console.log(_this.checkboxModel);

}else{//实现全选
console.log(ischeck)
_this.checkboxModel = [];
_this.checkboxData.forEach(function(item) {
_this.checkboxModel.push(item.id);
});
}
},
checkBtn:function(event){
var btnDom = event.currentTarget;
var _this = this;
if(btnDom.innerHTML == "全选")
{
_this.checkboxModel = [];
_this.checkboxData.forEach(function(item) {
_this.checkboxModel.push(item.id);
btnDom.innerHTML = "取消全选"
});
}
else
{
_this.checkboxModel = [];

btnDom.innerHTML = "全选"
}
}
}

})

var vm1 = new Vue({
el: '#app1',
data: {
checkedNames: []
}
})
</script>
</body>
</html>

sticky-footer的含义就是用在手机端,如果内容过短 footer就固定在底部 ,如果内容长footer就跟着页面走

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0" />
<title>sell</title>

<style>
*{margin:0; padding: 0;}
body,html{height: 100%}
.clearfix{display: inline-block;}
.clearfix:after {content: "."; display: block; height:0; clear:both; visibility: hidden;}
.detail-wrapper{min-height: 100% ; width: 100%}
.detail-wrapper .detail-main{margin-top: 64px; padding-bottom: 64px; text-align: center; color: #000;}
.detail-wrapper .detail-main p{text-align: center;}
.detail-close{position: relative; width: 32px; height: 32px; margin:-64px auto 0 auto; clear: both; font-size: 32px;}
</style>
</head>

<body>

<div class="detail-wrapper clearfix">
<div class="detail-main">
<p>111111111</p>
<p>111111111</p>
<p>111111111</p>
<p>111111111</p>
<p>111111111</p>
<p>111111111</p>
</div>
</div>
<div class="detail-close">X</div>
</body>

</html>

方法1:

1
2
3
直接 html {height: 100%; margin-bottom: 1px; }<br>
或者body { overflow-y: scroll;}<br>
缺点:页面会默认有滚动条的槽<br>

完美解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
html {
overflow-y: scroll;
}

:root {
overflow-y: auto;
overflow-x: hidden;
}

:root body {
position: absolute;
}

body {
width: 100vw;
overflow: hidden;
}

call/apply方法继承

第一种方法也是最简单的方法,使用call或apply方法,
将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  function Animal(name){
    this.name = name;
this.spec = '动物';
this.run = function(){
console.log(this.name + '是' + this.spec + ' ,它可以跑步')
}
   }


function Cat(name,color){
console.log(arguments) //["猫", "黄色"]
Animal.call(this,arguments) //这里其实也是不传arguments
    this.name = name;
    this.color = color;
   }

var cat = new Cat('猫','黄色');
cat.run() //猫是动物 ,它可以跑步

**prototype模式 **

如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。

1
2
3
4
Cat.prototype = new Animal();//相当于完全删除了prototype 对象原先的值,然后赋予一个新值
Cat.prototype.constructor = Cat //constructor指回Cat 防止继承链的紊乱
var cat = new Cat('猫','黄色');
cat.run() //猫是动物 ,它可以跑步

ES6继承

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
class AnimalEs6 {
constructor(props){
this.name=props.name||'未知';
}
eat(){
console.log(this.name+"在吃东西...");
}
}

class Bird extends AnimalEs6 {
constructor(props){
super(props)
this.name=props.name||'未知';
this.color = props.color;
}

fly(){
console.log(this.name + "在飞。。。")
}
}

var bird = new Bird({
name : '花花',
color : '白色'
})

bird.eat()
bird.fly()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
     <!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<input id="inputObj" />
<script type="text/javascript">

var inputObj = document.getElementById('inputObj')
var timer = null;
inputObj.addEventListener('input',function(){

if(timer){
clearTimeout(timer)
}
timer = setTimeout(function(){
console.log(inputObj.value)
},500)
},false)
</script>
</body>
</html>

用anywhere快速搭建本地服务器

1
npm install anywhere -g

之后找到你想搭建服务器的路径,然后再当前路径下输入:

1
anywhere 8860

然后浏览器就自动打开本地访问网址,一个简单的node服务器就这样被我们搭建好啦!