前端笔记

一、 ECMAScript 6语法

参考见 https://es6.ruanyifeng.com/

1 ECMAScript和JavaScript的关系

在1996年以前,浏览器是不支持脚本语言的,只支持html和css。

NetScape网景公司提出了前端编程语言,为了蹭java的热度,起名为JavaScript。并且经过与Sun公司的授权,网景公司将JavaScript注册为商标。

1996年,网景公司将该脚本提交给标准委员会ECMA。1997年,ECMA将其命名为ECMAScript。因此ECMAScript是国际标准,JavaScript是标准的实现。

2 ES6与ES5

2011年,ECMAScript5.1发布。自此以后,新的标准更新特别频繁,以后统称为ES6。

目前的主流浏览器都支持ES5,但是对ES6的支持不全面。

Bable是一个ES6转码器,可以将ES6代码转为ES5代码,从而在老的浏览器执行。

Bable的配置文件时.babelrc,存放在项目的根目录下。配置文件的基本规则是

{
  "presets": [],
  "plugins": []
}

3 let和const

js中定义变量用var,var存在变量提升现象,即var变量可以在声明之前使用。

let不存在变量提升。建议用let和const。const表示常量,let表示变量。

var1
var var1 = 100; //关键词var定义的变量是全局的


// var2            // 因为var2没有定义,所以报错
let var2 = 200; //该处使用let定义变量var2


const  name = '张三'
// name = '里斯'        //使用const定义的常量不能被重写赋值

4 块级作用域

es5只支持全局作用域和函数作用域,es6增加了块级作用域。

var a = 19;
{
    var a = 20;
}
console.log('输出a=', a)    //输出a=20


let  b=10;
{
    let b=30;
}
console.log('输出b=', b)    //输出b=10   //块级作用域的范围仅限于{...}之内

5 解构赋值

类似于多重赋值,使用符号[]或者{}。

let a,b = [1,2]     //定义了一个变量a,没有赋值;定义了一个变量b,赋值了
console.log('a=', a, 'b=',b)    //a= undefined b= [ 1, 2 ]

let [c,d] = [1,2]     //解构,使用[]
console.log('c=', c, 'd=',d)    //c= 1 d= 2

// 对象的结构,使用{}
let {name, age} = {'name':'wuchao', 'age':23}
console.log('name=', name, 'age=',age)

let {id, ...other} = {'id':10, 'name':'wuchao', 'age':23, 'address':'北京市', 'school':'ucas'}
console.log('id=', id, 'other=',other)  //id= 10 other= { name: 'wuchao', age: 23, address: '北京市', school: 'ucas' }

// 提取json数据
let jsonData = {id:42, name:'吴超'}
let {id, name} = jsonData

对象的解构赋值,可以很方便的将现有对象的方法,赋值到某个变量。在node中很常用。

// 把Math类中的三个方法解构赋值
let {log, sin, cos} = Math;
// 把console类中的log方法解构赋值;
const {log} = console;
log('hello')

// 下面是输入模块的指定方法
const {SourceMapConsumer, SourceNode} = require('source-map')

6 字符串的扩展

6.1 es6支持unicode

比如“\u0061”表示”a“。

6.2 遍历字符串

// 遍历字符串
for(let i of 'foo'){
  console.log(i)
}

6.2 模板字符串

let name ='张三' , age =23

let s1 = '我的名字叫'+name+',年龄是'+age   // 字符串拼接
console.log('s1=', s1)

let s2 = '我的名字叫${name},年龄是${age}'  // 这里使用单引号,不会解析变量
console.log('s2=', s2)

let s3 = `我的名字叫${name},年龄是${age}`  // 使用飘号,可以解析里面的变量
console.log('s3=', s3)

6.3 标签模板

标签模板相当于函数调用。飘号相当于函数的参数。

alert`hello`  等同于  alert(['hello'])

6.4 新增方法

let s = 'Hello world!'

s.includes('o')	//true
s.startsWith('Hello')	//true
s.endsWith('!')	//true

'x'.repeat(3)	//'xxx'
'x'.padStart(5, 'ab')	//'ababx'
'x'.padEnd(5, 'ab')	//'xabab'

'   abc   '.trim()	//'abc'

7 函数的扩展

7.1 参数的默认值

es6之前的函数参数,不能使用默认值。

function log(x, y='world'){...}

7.2 与解构赋值默认值结合使用

// 定义
function foo({x, y=5}){...}
// 调用
foo({})	//undefined 5
foo({x:1}) //1 5
foo({x:1, y:2}) // 1 2
foo() // TypeError
// 定义
function fetch(url, {body='', method='GET', headers={}}){...}
// 调用
fetch('http://www.baidu.com', {})	//GET
fetch('http://www.baidu.com')	//报错

7.3 rest参数

es6引入rest参数(形式为 …变量名),用于获取函数的多余参数。rest参数搭配的变量是一个数组。

function add(...values)
add(1,2,3)

7.4 箭头函数

// 有名函数
function f1(a, b){return a+b;}

// 箭头函数
(a, b)=>{
    console.log('进入了箭头函数', )
    return a+b
}
// 简洁写法
(a, b)=>a+b

// 常用作匿名函数
[1,2,3].map(x=>x*x)

// 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
function foo(){
  setTimeout(()=>{
    console.log(this.id)
  }, 100)
}

var id=21
foo.call({id:42}) // 42

8 数组扩展

9 对象的扩展

ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

9.1 属性简写

const name = "吴超"
const baz = {name}  // {name:"吴超"}

上面的代码中,变量foo直接写在大括号中。这时,属性名就是变量名,属性值就是变量值。

function f1(name, age){
    return {name, age}
}
f1("张三", 23)  // Object {name:'张三', age:23}

9.2 方法简写

以前的写法是

{
  f1:function(){return "f1"}
}

新的写法是

{
  f1(){return "f1"}
}

9.3 对象的定义

现在,使用属性、方法的简写,就可以非常方便的定义一个对象

var id = 1
const person = {
  id,
  name:'张三',
  say(msg){
     return 'hello '+msg
  }
}

9.4 链判断

如果对象嵌套对象,读取最内层对象的属性msg.body.user.name,如果一个对象不存在,就报错。

使用链判断运算符?.,不存在,则返回undefined,最好指定默认值。

// 对象判断链
msg?.body?.user?.name|| 'default'
// 数组判断链
obj?.[1]
// 函数判断链
a?.b()

10、Symbol

11 Set和Map数据结构

12 Proxy

13 Promise对象

14 迭代器和for…of循环

二、 Node语法

1 引言

1.1 nodejs是js的运行环境

nodejs是一个基于chrome v8引擎的js运行环境。

nodejs是让js运行在服务端。nodejs是服务端的开发框架。 bs/cs

1.2 特点

事件驱动、异步处理、非阻塞、高并发、单进程单线程

“拉”模式

“推”模式

1.3 组成

v8、libuv(event loop)、js库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDfSr8Qz-1608279639345)(vuejs学习笔记.assets/u=2398826131,3965435416&fm=26&gp=0.jpg)]

1.4 安装

不建议装到有中文有空格的路径下。

vs code

1.5 基本命令

.exit 退出命令行的方法

2 全局对象

global对象,等价于浏览器中的windows对象,是全局的。

2.1 全局变量

console.log('文件名', __filename)// 文件名 F:\学佳澳\高校实训20-24日\代码\day0106_global全局变量.js
console.log('目录位置', __dirname)  //目录位置 F:\学佳澳\高校实训20-24日\代码

2.2 全局函数

setTimeout(cb,ms)只调用一次函数

t = setInterval(cb, ms) 间隔指定时间,周期性的执行函数。

clearTimeout(t) 停止前面的周期调用

// 只执行一次函数调用,5000表示5秒钟后执行这个函数
setTimeout(()=>console.log('只输出一次', ), 5000)

// 周期性执行某个函数,时间单位是毫秒,1000表示1秒钟
// let t = setInterval(()=>console.log('输出我', ), 1000)

// clearInterval(t)    // 停止前面的周期性调用

2.3 console对象

是用的最频繁的一个node对象

// 日志输出级别, fatal
// console.log(, )
// console.error(, )
// console.info(, )

// 分组显示日志
console.group('aaaa')
console.log('Runoob,这个在分组里面。')
console.groupEnd()

function f2(a,b){return a+b;}
// 显示函数的调用环境
console.trace('函数调用', f2(1,2)) 


// 显示一个对象的所有信息,可以与console.log(...)的输出信息,做比较
console.dir({'name':'zhangsan', 'age':23})

function f1(a,b){return a+b;}
//统计函数的执行时间
console.time('flag1')// 记录一个开始时间
f1(1,2) //函数整个执行过程
console.timeEnd('flag1')// 记录一个结束时间

2.4 process对象

获取操作系统的进程信息,与操作系统交互

console.log('进程号', process.pid)
console.log('进程名', process.title)
console.log('CPU架构', process.arch)
console.log('操作系统', process.platform)
console.log('当前的工作目录', process.cwd())
// process.chdir()    切换工作目录
// process.kill()       杀死进程
// process.abort()      终止进程

3 异步回调

nodejs是异步执行,异步是通过什么体现的哪?通过回调函数。

但是,通常理解,回调不一定是异步

//前面讲的定时器就是异步
setTimeout(()=>console.log('异步执行', ), 1000)

console.log('程序结束', )

讲一下nodejs中的同步和异步操作

let fs = require('fs')
// 同步操作
// const data = fs.readFileSync('day0101_let和const.js')
// console.log('输出结果', data.toString())



// 异步操作
const data = fs.readFile('day0101_let和const.js', (err, data)=>{
    if(err){console.log('出错了', err);return;}
    console.log('输出结果', data.toString())
})
console.log('--------------------------------------', )

4 事件循环

设计上应该是借鉴了操作系统的思路。

事件机制中,由三部分组成:事件、事件发出、事件接收处理

nodejs负责接收事件、处理事件

我们可以发出事件、自定义处理事件的方法、注册事件处理方法

// 引入events事件模块
const events = require('events')
// 创建类EventEmitter的对象
var eventEmitter = new events.EventEmitter()

// 事件机制中,由三部分组成:事件、事件发出、事件接收处理
// nodejs负责接收事件、处理事件
// 我们可以发出事件、自定义处理事件的方法、注册事件处理方法
eventEmitter.on('event1', ()=>{
    console.log('处理事件', )
});

// 触发事件
eventEmitter.emit('event1');

事件机制,可以让行为和操作相分离,也属于解耦的一种方式。

5 模块系统

编程语言自身提供一些开发包/api,更复杂的更多的功能需要模块提供。

一般一个js文件,就可以作为一个模块。模块间可以互相调用。

关键词:exports、module.exports、require

  • require:是node和es6都支持的;
  • export/import:只有es6支持,node不支持
  • exports/module.exports:只有node支持,es6不支持

简单理解:exports = module.exports = {}

下面是模块文件modue01.js

// module.exports = name = '吴超'   // 导出变量

// module.exports = add = (x,y)=>x+y    // 导出函数

class Student{
    constructor(id, name){
        this.id = id;
        this.name = name;
    }
}

// module.exports = stu = new Student(12, '张三')   // 导出对象

下面是调用部分

const aaa = require('./module01')
console.log('输出', aaa)

// console.log('姓名', name)
// console.log('执行函数', add(1,2))
// console.log('学生', stu)

三、 npm

npm(node package manager)是nodejs的 包管理工具,用于node插件的管理(包括安装、卸载、管理依赖等)。

npm是随同nodejs一起安装的管理工具。

java领域有类似的是maven,linux领域有类似的yum、rpm

因为nodejs做的很小巧、高内聚。为了丰富功能,通过package的形式扩展。

使用npm下载模块的时候,自动下载依赖的模块和对应的版本。

1 初始化项目

通过初始化项目,可以创建一个package.json文件。

npm init 项目名称

在package.json里面记录了项目依赖的模块。当把项目给别人时,别人执行npm install即可把依赖项安装,非常方便。

2 安装模块

需要知道模块名称即可。

npm   install   xxx

在国外有一个nodejs的模块仓库,免费的,谁都可以下载。当我们执行install的时候,就去这个仓库寻找特定的模块和指定的版本。下载到本地。

下载到本地项目的node_modules文件夹中,同时注册到package-lock.json文件中。

使用参数-g可以安装到全局。

npm  root  -g	//可以查看全局模块库的位置

npm  install  xxx -g

npm  uninstall   xxx    //卸载模块

3 package.json和package-lock.json

npm5之前,不会生成pacakge-lock.json文件。

package.json文件锁定的是大版本,不关心小版本。

package-lock.json文件锁定小版本。

4 运行任务

使用npm可以运行package.json中的任务

{
    "scripts":{
        "task1":"node -v"
      }
}

在命令行运行

npm    run    task1

npm start和是npm run start的简写形式。

在一个npm管理项目中,一般默认有start的定义,且会经常使用,所以就在npm执行中简化输入目的设置了npm run start的简写,类似的还有npm stop、npm test等等。而其他的一些不太通用的命令项则只能通过npm run <命令项>的形式执行。

5 使用国内的npm源

使用淘宝镜像

npm  i  cnpm  -g  --registry=https://registry.npm.taobao.org

6 使用npm源的切换工具nrm

npm   i  nrm  -g		// 安装nrm
nrm   ls	//查看有哪些源仓库
nrm   use   taobao	//指定使用哪个源仓库

四、 Node常用模块

1、文件系统fs

api中由同步和异步两个版本,建议使用异步版本。

const fs = require('fs')

1.1 获取文件信息

比如可以判断是文件还是文件夹?还可以知道文件大小、创建者、创建时间、权限等等。

// 访问文件信息,在进行文件夹遍历的时候,需要查看类型和基本信息
fs.stat('module01.js', (err, data)=>{
    console.log('类型', data.isFile()?'文件':'文件夹')
    console.log('文件信息', data)
})

1.2 读文件

读文件的时候,回调函数中,首先要进行错误判断,如果有错误,输出错误原因后返回;

fs.readFile('module01.jsasfd', (err, data)=>{
    if(err){
        console.error('出错了', '文件不存在')
        return
    }
    console.log('文件内容', data.toString())
})

1.3 写文件

// 每次新创建一个文件。如果已经存在,则先删除再创建
fs.writeFile('a.txt', '我的名字叫吴超,年龄23',()=>{})

// 追加
// fs.writeFile('a.txt', '我的名字叫吴超,年龄23',{flag:'a'}, ()=>{})

2、Buffer类

在js中,只有字符串类型,没有二进制类型。

如果要读写图片、音视频文件的时候,就需要使用Buffer类。

学习api的时候,把Buffer想象成String类。

//方法1:创建指定大小的字节空间,指定填充的值,默认是0
let bf1 = Buffer.alloc(10, 1)
console.log('bf1= ', bf1)
// 方法2:根据字节数组创建
let bf2 = Buffer.from([1,2,3,4,5])
console.log('bf2=', bf2)
// 方法3:把字符串转为Buffer
let bf3 = Buffer.from('hello world我的祖国')
console.log('bf3 = ', bf3)

// 输出,指定输出格式
console.log('bf1 = ', bf1.toString('hex'))
// 输出字符串的时候,可以指定编码格式
console.log('bf3 = ', bf3.toString('utf-8'))

3、Stream类

Stream类是个抽象接口,有四种类型:Readable、Writable、Duplex、Transform类型。

所有的Stream对象都是EventEmitter的实例。

3.1 读流

const  fs = require('fs')

const readStream = fs.createReadStream('module01.js')
// 注册事件
readStream.on('data', (chunk)=>{
    console.log('有数据了', chunk.toString())
})
// 注册事件
readStream.on('end', ()=>{
    console.log('读取结束')
})
// 注册事件
readStream.on('error', (error)=>{
    console.error('出错了', error)
})

3.2 写流

const fs = require('fs')

const writeStream = fs.createWriteStream('b.txt')

writeStream.on('finish', ()=>console.log('写完了'))

writeStream.write('hello world我的祖国')
// 调用end方法时,触发finish事件
writeStream.end('完成写入数据');

3.3 压缩解压缩

使用第三方库compressing,支持windows和linux。

先安装库 npm install compressing

const compressing = require('compressing')

// 压缩文件夹
// compressing.zip.compressDir('F:/DRMsoft', "DRMsoft.zip")
// compressing.zip.compressFile()

// 解压缩
compressing.zip.uncompress("DRMsoft.zip", __dirname)

4、案例:复制文件夹

知识点:路径的遍历、判断文件类型、判断文件是否存在、创建文件夹、数据复制

const fs = require('fs')

// 复制工具
var copyTool = function(src, dst, callback){
    if(! fs.existsSync(src)){//src不存在
        console.error('错误:', '文件不存在')
        return;
    }
    fs.exists(dst, (isExist)=>{
        // if(isExist){    //存在目标文件夹
        //     callback(src, dst)
        // }else{  //不存在目标文件夹
        //     fs.mkdir(dst, ()=>callback(src, dst))
        // }
        if(! isExist){
            fs.mkdirSync(dst)
        }
        callback(src, dst)
    })
};

var copy = function(src, dst){
    fs.readdir(src, (err, names)=>{ // 读取文件夹
        // console.log('读取的内容', names)
        names.forEach( name=>{
            // console.log('读取的名称', name)
            let _src = src+'/'+name
            let _dst = dst+'/'+name
            fs.stat(_src, (err, st)=>{
                // console.log('读取的名称', _src, st.isFile()?'文件':'文件夹')
                if(st.isFile()){    //如果碰到是文件,下面完成的时是复制功能
                    // fs.createReadStream(_src).pipe(fs.createWriteStream(_dst))

                    let _readStream = fs.createReadStream(_src)
                    let _writeStream = fs.createWriteStream(_dst)
                    //管道
                    _readStream.pipe(_writeStream)
                }else if(st.isDirectory()){
                    copyTool(_src, _dst, copy)
                }
            })
        } )
    });
}

copyTool('F:/DRMsoft', __dirname+'/aaa', copy)

5、GET请求

网络请求,知道请求路径是什么,请求参数是什么,可以响应结果。

http://localhost:3000/hello/?name=wuchao&age=23

5.1 引入的模块

const http = require('http')
const url = require('url')
const util = require('util')

5.2 解析请求路径和请求参数

http.createServer((req, res)=>{
    let method = req.method //值常见的有GET\POST
    console.log('请求方法', method)

    // 这里的第二个参数必须是true
    let _url = url.parse(req.url, parseQueryString=true)
    console.log('请求路径', _url.pathname)
    console.log('请求参数是', _url.query.name, _url.query.age)
    // res.end(util.inspect(_url))
}).listen(3000, console.log('服务器启动了'))

5.3 返回html页面

const http = require('http')

htmlcode1 = `
<form action="http://localhost:3000/asadfadsf"  method="GET">
<input type="text" name="name" value="zhagnsan">
<input type="submit" value="提交">
</form>
`

http.createServer((req, res)=>{
    // 必须指定响应头,浏览器才能解析html
    res.writeHead(500, {'Content-Type':'text/html; charset=utf8'})
    // 调用write或者end写应答
    res.end(htmlcode1)
}).listen(3000)

6、POST请求

POST请求是Stream,基于事件的。

解析POST请求的数据,使用querystring模块中的parse方法。

const http = require('http')
const querystring = require('querystring')

let form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
    保存
</button>
</form>
`

http.createServer((req, res)=>{
    let data = '';  //data保存post中的数据
    req.on('data', chunk=>{
        data+=chunk;
        // console.log('到来的数据', chunk.toString())
    })
    req.on('end',()=>{
        // 解析post数据
        let _data = querystring.parse(data)
        console.log('解析post数据', _data)
    })

    res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
    res.end(form_html)
}).listen(3000, console.log('服务器启动了', ))

7、重定向

为了解决重复提交,使用重定向。

在应答中使用302状态码,指定新的访问地址

res.writeHead(302, {'Location':'http://localhost:3000/'})

下面是完整的代码

const http = require('http')
const querystring = require('querystring')

let form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
    保存
</button>
</form>
`

http.createServer((req, res)=>{
    if('GET'==req.method){
        res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
        res.end(form_html)
    }else if('POST'==req.method){
        let data = '';  //data保存post中的数据
        req.on('data', chunk=>{
            data+=chunk;
            // console.log('到来的数据', chunk.toString())
        })
        req.on('end',()=>{
            // 解析post数据
            let _data = querystring.parse(data)
            console.log('解析post数据', _data)
            res.writeHead(302, {'Location':'http://localhost:3000/'})
            res.end()
            return
        })
    }

}).listen(3000, console.log('服务器启动了', ))

8、读写json文件

// 把string转为json形式,把json形式转为string

//把json形式转为string
// console.log('输出字符串', typeof JSON.stringify({'name':'wuchao', 'age':23}))

// 把string转为json形式
// console.log('输出json格式', typeof JSON.parse('{"name":"wuchao","age":23}'))

const fs = require('fs')
// 读json文件
fs.readFile('data.json', (err, data)=>{
    let jsondata = JSON.parse(data.toString())
    console.log('输出文件中的内容', jsondata,  jsondata.name, jsondata.age)
})

9、案例:学生信息管理系统

用http、fs、url、querystring等模块,做一个学生信息管理系统,

【1】用户通过get访问首页,服务器从json文件中加载数据,返回学生列表;

【2】用户在页面中点击“添加“, 发送get请求,服务器返回需要填写信息的表单;

【3】用户填写表单,点击提交,发送post请求到服务器,服务器解析数据保存到磁盘文件;重定向到学生列表;

const fs = require('fs')
const http = require('http')
const url = require('url')
const querystring = require('querystring')



table_html = `
<table border="1px solid red" style="width:500px">
<thead>
    <tr><th>姓名</th><th>年龄</th></tr>
</thead>
<tbody>
    <tr><td>aaaa</td><td>aaaa</td></tr>
    <tr><td>aaaa</td><td>aaaa</td></tr>
</tbody>
</table>
<a href="http://localhost:3000/toadd">添加</a>
`

form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
    保存
</button>
</form>
`

function readJSON(){
    let data = fs.readFileSync(__dirname+'/data.json').toString()
    data = JSON.parse(data)
    console.log('读取到的JSON数据', data)
    return data
}

function fillTable(jsondata){
    head = `<table border="1px solid red" style="width:500px">
    <thead>
        <tr><th>姓名</th><th>年龄</th></tr>
    </thead>
    <tbody>`
    
    jsondata.forEach((stu, index)=>{
        //let row = '<tr><td>'+stu.name+'</td><td>'+stu.age+'</td></tr>'
        let row = `<tr><td>${stu.name}</td><td>${stu.age}</td></tr>`
        //console.log('第'+index+'行', row)
        head += row
    })
    
    tail = `</tbody>
    </table>
    <a href="http://localhost:3000/toadd">添加</a>`
    return head+tail
}

function writeJSON(stu){
    let data = readJSON()
    data.push(stu)
    fs.writeFileSync(__dirname+'/data.json', JSON.stringify(data))
}

function parsePOST(req, writeJSON){
    let data = ''
    req.on('data', (chunk)=>{data+=chunk})
    req.on('end', ()=>{
        data = querystring.parse(data)
        // console.log('post参数', data.name, data.age)
        writeJSON(data)
    })
}


function route(req, res){
    let method = req.method
    let pathname = url.parse(req.url).pathname
    
    switch(pathname){
        case '/':
            table = fillTable(readJSON())
            res.end(table)
            break;
        case '/toadd':
            res.end(form_html)
            break;
        case '/save':
            parsePOST(req, writeJSON)
            res.writeHead(302, {'Location':'http://localhost:3000/'})
            res.end()
            break;
        default:
            res.end('错误的请求路径 '+pathname)
    }
}

function init_app(){
    if(! fs.existsSync(__dirname+'/data.json')){
        fs.writeFileSync(__dirname+'/data.json', '[]')
    }
}
init_app()

http.createServer((req, res)=>{
    res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
    route(req, res)
}).listen(3000, console.log('启动服务器,监听3000端口.....'))

五、 express框架

express是基于nodejs的web框架。

1 极简入门

可以在修改代码的时候,重启应用nodemon xxx.js

//加载模块
const express = require('express')
// 创建web服务器
const app = express()
// 启动服务器
app.listen(3000, console.log('服务器启动了,监听3000端口' ))

2 返回各种类型的应答

// 响应get请求
app.get('/', (req, res)=>{
    // 输出文本
    // res.send('hello express')

    // 输出json
    // res.send({'name':'wuchao', 'age':23})

    // 输出html文件,使用绝对路径
    // res.sendFile(__dirname+'/index.html')  

    // 渲染模板
    // res.render(...)
})

3 解析GET请求

对于普通的get请求,如http://localhost:3000/?name=wuchao&age=23,使用req.query 获得get请求信息。

对于url路径中含有数值的,即有名路径,使用req.params获得

// 普通路径
app.get('/', (req, res)=>{
   res.send(res.query.name)
})
// 有名路径
app.get('/user/:id', (req, res)=>{
    res.send(req.params.id)
})

4 解析POST请求参数

引入第三方模块body-parser,然后使用req.body解析参数。

// 引入模块
const bodyparser = require('body-parser')
// 使用中间件,处理application/x-www-form-urlencoded
app.use(bodyparser.urlencoded({extended:false}))

app.get('/', (req, res)=>{
    // 获取GET请求参数
    console.log('GET请求参数', req.query)
    res.sendFile(__dirname+"/pages/index.html")
})

app.post('/save', (req, res)=>{
    // 获取POST请求参数
    console.log('POST请求参数', req.body)
})

5 解析json参数

app.use(express.json())

6 路由

当请求路径特别多的时候,我们需要对每一个请求单独做出响应,这时候就会产生大量的代码,堆积在一起。使用路由,可以让请求的路径结构更加清晰,同时也可以分模块处理。

思路:对url进行分级,分为多个大的模块,每个模块下面分为好多请求

/stu/list /stu/add /stu/save /stu/delete

定义一个路由模块stu.js

const express = require('express')

// 定义了一个一级路由
const stu = express.Router()
// 定义二级路由
stu.get('/list', (req, res)=>{
    console.log('/stu/list', )
    res.send('/stu/list')
})
stu.get('/add', (req, res)=>{res.send('/stu/add')})

module.exports=stu

在服务器模块中,使用路由模块

const express = require('express')
const app = express()
const stu = require('./stu')

// 告诉服务器,使用一级路径/stu
app.use('/stu', stu)

app.listen(3000, console.log('the server is running....', ))

7 模板引擎art-template

常用的操作:输出、判断、循环

art-template模板既可以用在前端js,也可以用在后端js。

7.1 使用模板

安装模板

npm install  art-template   express-art-template

加载中间件

// 使用中间件,使用express-art-template模板
// 模板文件后缀是html
app.engine('html', require('express-art-template'))

// 模板文件默认存放在views目录下,也可以修改目录
app.set('views', 目录路径)

// 如果输出页面时使用res.render(__dirname+'...') 这种绝对路径,那么关于views的设置无效

输出模板文件的时候,必须使用res.render方法,否则不会渲染模板文件中的语法。

res.render(__dirname + "/pages/index.html", {'stulist': stulist});

7.2 模板语法

if是用于判断的。

{{if msg}}
	{{msg}}
{{/if}}

each是循环数组stu,stu里面的每一个元素是一个json,在循环体内使用$value表示每一个循环遍历,使用 ¥ index表示循环序号。

{{each stu}}
  <tr><td>{{$value.name}}</td><td>{{$value.age}}</td></tr>
{{/each}}

7.3 模板继承

模板继承允许构建 一个模板的骨架,然后向里面填充组成部分。

基本语法如下,在骨架模板中适宜{{block}}作为占位符,在继承模板中使用{{block}}定义具体的内容。

{{extend './layout.art'}}
{{block 'head'}} ... {{/block}}

下面是骨架模板

<!--layout.art-->
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>{{block 'title'}}My Site{{/block}}</title>

    {{block 'head'}}
    <link rel="stylesheet" href="main.css">
    {{/block}}
</head>
<body>
    {{block 'content'}}{{/block}}
</body>
</html>

下面是继承后的模板

<!--index.art-->
{{extend './layout.art'}}

{{block 'title'}}{{title}}{{/block}}

{{block 'head'}}
    <link rel="stylesheet" href="custom.css">
{{/block}}

{{block 'content'}}
<p>This is just an awesome page.</p>
{{/block}}

7.4 包含模板

把某一个公共部分,加入到当前页面中来。

{{include './header.art'}}
{{include './header.art' data}}

7.5 过滤器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UR6Mfeq5-1608279639349)(vuejs学习笔记.assets/1281517-20180205155942998-197158086.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5SpHacp-1608279639352)(…/…/…/_学习笔记/前端笔记/nodejs/1281517-20180205160104326-1983829336.png)]

8 会话管理

使用第三方模块express-session来管理会话。

session是另一种记录客户状态的机制,与cookie保存在客户端浏览器不同,session保存在服务器当中;

当客户端访问服务器时,服务器会生成一个session对象,对象中保存的是key:value值,同时服务器会将key传回给客户端的cookie当中;

当用户第二次访问服务器时,就会把cookie当中的key传回到服务器中,最后服务器会吧value值返回给客户端。

因此上面的key则是全局唯一的标识,客户端和服务端依靠这个全局唯一的标识来访问会话信息数据。

接下来,安装模块,并使用中间件

// npm install express-session		// 安装模块

// 导入模块
const session = require('express-session')
// 配置中间件
app.use(session({
    secret: "keyboard cat",
    resave: false,
    saveUninitialized: true,
    cookie: ('name', 'value',{maxAge:  5*60*1000,
    secure: false})
}))

接下来就可以使用req.session了

// 赋值
req.session.user = '吴超'
// 取值
req.session.user
// 销毁会话
req.session.destroy((err)=>{res.send('销毁')})

会话的配置项有

  • name - cookie的名字(原属性名为 key)。(默认:’connect.sid’)
  1. store - session 的存储方式,默认存放在内存中,也可以使用 redis,mongodb 等。express 生态中都有相应模块的支持
  2. secret - 通过设置的 secret 字符串,来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
  3. cookie - session cookie设置 (默认:{ path: ‘/‘, httpOnly: true,secure: false, maxAge: null })
  4. genid - 生成新session ID的函数 (默认使用uid2库)
  5. rolling - 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
  6. resave - 强制保存session即使它并没有变化 (默认: true, 建议设为:false)
  7. proxy - 当设置了secure cookies(通过”x-forwarded-proto” header )时信任反向代理。当设定为true时,
    ”x-forwarded-proto” header 将被使用。当设定为false时,所有headers将被忽略。当该属性没有被设定时,将使用Express的trust proxy。
  8. saveUninitialized - 强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。在设定一个cookie前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默认:true)
  9. unset - 控制req.session是否取消(例如通过 delete,或者将它的值设置为null)。这可以使session保持存储状态但忽略修改或删除的请求(默认:keep)

9 中间件完成权限控制

使用中间件完成权限控制。

app.use((req, res, next)=>{
    console.log(req.url, )
    if(没有权限){
        res.redirect('/')	//重定向
        return;
    }
    next()	// 放行
})

10 数据范围

数据的范围可以是res、session或者app。指定数据范围的目的是为了向页面传值,以及在不同的express方法之间传值。

应用级别使用app.locals,会保存在应用的整个生命周期;

响应级别使用res.locals,会保存在本次请求应答的生命周期中;

app.locals
res.locals.session = req.session

11 上传

使用第三方模块multer

首先看一下页面的结构

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="content" />
    <input type="submit" value="上传文件" />
</form>

接下来看一下js部分的代码

// 安装模块
// npm install multer
// 导入模块
var multer  = require('multer')
// 创建multer实例,指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});
// 以下是接收上传信息,upload.any()是个能够接收任何上传的中间件,
app.post('/upload', upload.any(), (req, res)=>{
	//req.files接收所有的文件,这是个数组。里面的文件有个属性path表示上传后的路径,可以复制到其他位置
    console.log('上传文件信息', req.files)
})

接下来是完整的代码

var fs = require('fs');
var express = require('express');
var multer  = require('multer');

var router = express.Router();
// 指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});

router.post('/', upload.any(), function(req, res, next) {
    console.log(req.files[0]);  // 上传的文件信息

    var des_file = "./upload/" + req.files[0].originalname;
    fs.readFile( req.files[0].path, function (err, data) {
        fs.writeFile(des_file, data, function (err) {
            if( err ){
                console.log( err );
            }else{
                response = {
                    message:'File uploaded successfully',
                    filename:req.files[0].originalname
                };
                console.log( response );
                res.end( JSON.stringify( response ) );
            }
        });
    });
});

module.exports = router;

multer的配置有:

  • dest或storage 存储文件的位置
  • fileFilter 文件过滤器
  • limits 限制上传的数据
  • preservePath 保存包含文件名的完整文件路径

12 文件下载

下载文件只需要使用==res.download(文件路径,下载文件名,回调函数)==即可

app.get('/download',function (req,res) {
    res.download(__dirname+'/route.js','route.js',
    function (err) {
        if(err){
          console.log(err)
       }
    })
})

14 验证码

使用第三方模块svg-captcha

// npm install svg-captcha   // 安装模块
// 导入模块
const svgCaptcha = require('svg-captcha')

router.get('/static/captcha', (req, res)=>{
    let captch = svgCaptcha.create({
        color: true,    // 彩色
        width:100,      // 宽度
        height:60,      // 高度
        fontSize:48,    // 字体大小
        size:4,         // 验证码个数,这里的captch.text是4个字母
        noise:3,        // 干扰线条
        ignoreChars:'0o1ilI'    // 不包括的字符
    });
    // 保存到session中,登录时取出校验
    req.session.captcha = captch.text.toLocaleLowerCase();
    // 防止使用模板:指定输出类型
    res.type('html')
    // 这里的captch.data 是一个svg的html内容。指定输出内容
    res.end(captch.data)
});

如果要在页面上输出

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
	var captcha = function(){
		$.get('http://localhost:3000/captcha', data=>{
			$('#captchaImg').html(data)
		})
	}
	$(function(){
		captcha()
	   $('#captchaImg').click(()=>{
		captcha()
	   })
	});
</script>

// 假设页面有下面这个span
    <form action="/login" method="post">
        用户名:<input type="text" name="username" id="username" value="张三">
        <br/>
        密码:<input type="password" name="password" id="password" value="admin">
        <br/>
        验证码:<input type="text" name="captchaCode" id="captchaCode"><span id="captchaImg"></span>
        <br>
        <input type="submit" value="登录">
    </form>

15 响应静态文件

所谓的静态文件,指的是css、js、各种图片。

静态文件通常放在一个统一的文件夹中,比如有如下的目录结构

/static/js/index.js

那么,我们使用 app.use(express.static(‘static’)) 表示static目录。相当于隐藏了指定的目录。

这样,在地址栏就可以使用http://localhost:3000/js/index.js 就可以访问到该文件。在页面中引用该js的话,也应该使用 “/js/index.js” 的形式。

还可以使用app.use(‘/public’, express.static(‘static’)) 表示static目录。注意前面的public必须使用**/**开始。

在地址栏就需要使用http://localhost:3000/public/js/index.js 就可以访问到该文件。

16 跨域请求

安装模块cors,增加下面一行即可。

app.use(require('cors')())

17 mysql操作

使用第三方模块mysql

var mysql = require('mysql');
 
var pool = mysql.createPool({
    connectionLimit: 10,
    host: 'localhost',
    port: '3306',
    user: 'root',
    password: '',
    database: 'xkdb'
});

pool.getConnection(function (err, connection) {
	if (err) throw err;

	var sqlStr='SELECT * FROM category';
	connection.query(sqlStr, function (err, rows,fields) {
		if (err) throw err;
		console.log('json', {results: JSON.stringify(rows),fields:JSON.stringify(fields)});

		connection.release();
	});
});

18 模块

分模块操作,可以让逻辑更加清晰。

const express = require(`express`)
const users = express.Router()

users.use((req, res, next) => {
  console.log(`路由执行成功啦~~~`, Date.now());
  next()
})

users.get(`/`, (req, res, next) => {
  res.json({
    status: 200,
    data: `请求成功`
  })
})

module.exports = users

在主文件中

//  使用路由 /user 是路由指向名称
import users from '...'
app.use(`/users`,users)

六、 案例:学生管理系统

1、 功能列表

  • 数据保存在mysql中
  • 首页是学生列表;
  • 没有权限时,只能看到学生列表,看不到修改、删除、添加
  • 没有权限时,都会跳转到首页的学生列表
  • 登录页面,用户名、密码、验证码;如果登录成功,跳转到首页
  • 有权限时,首页显示添加、修改、删除
  • 可以退出

2、 搭架子

  1. 创建一个文件夹stu-mis

  2. 进入这个文件夹,在命令行执行npx express-generator,生成模板项目

  3. 可以删除views文件夹中的所有模板文件。在命令行执行npm install art-template express-art-template,安装art模板引擎。

  4. 在命令行执行npm install,安装模块

  5. 修改app.js的内容的第14行

    app.set('html', require('express-art-template'));
    

    修改app.js的内容的第20行

    app.use('/static', express.static(path.join(__dirname, 'public')));
    

    这样,就可以使用/static访问静态内容了。

  6. 修改index.js文件,

      res.render('index.html');
    

    在views文件夹中创建index.html文件

  7. 在命令行执行nodemon,运行项目。在浏览器访问http://localhost:3000 就能看到首页面。

  8. 在vscode中安装 EJS language support 插件

    至此,架子搭建完毕。

3、显示表格

修改index.js内容

router.get("/", function (req, res, next) {
  stuList = [
    { id: 1, name: "张三", age: 23 },
    { id: 2, name: "李四", age: 24 },
  ];
  res.render("index.html", { title: "Express", stuList: stuList });
});

修改views文件夹中index.html内容

    <table>
        {{each stuList}}
        <tr>
            <td>{{$value.id}}</td>
            <td>{{$value.name}}</td>
            <td>{{$value.age}}</td>
        </tr>
        {{/each}}
    </table>

4、访问数据库

创建数据库访问模块db.js

let mysql = require('mysql')

let pool = mysql.createPool({
    host:'localhost',
    port:3306,
    user:'root',
    password:'admin',
    database:'test',
});

module.exports = function(sql, callback){
    pool.getConnection(function(err, conn){
        if(err){
            console.error('数据库连接错误', err)
            throw err;
        }
        conn.query(sql, function(err, result){
            if(err){
                console.error('执行语句错误', err)
                throw err;
            }
            console.log('执行结果',  result)
            callback(result);
            conn.release();
        });
    });
}

修改index.js中的内容

router.get("/", function (req, res, next) {
  function callback(rows){
    res.render("index", { title: "Express", stuList: rows });
  }
  db('select * from stu', callback);
});

这样,以后就可以非常方便的使用数据库了。

5、使用会话权限管理

在app.js中,使用会话中间件,这里的会话中间件,一定要位于前其他app.use(…)的前面。

// npm install express-session		// 安装模块

// 导入模块
const session = require('express-session')
// 配置中间件
app.use(session({
    secret: "keyboard cat",
    resave: false,
    saveUninitialized: true,
    cookie: ('name', 'value',{maxAge:  5*60*1000,
    secure: false})
}));
// 配置session中间件
app.use((req, res, next)=>{
  res.locals.session = req.session
  next()
});

权限管理

// 使用中间件完成权限控制
app.use((req, res, next)=>{
  if(req.url.startsWith('/static')){
    next();
    return;
  }
  if(req.url=='/login'){
      console.log('请求/login', )
      next()
      return;
  }
  if(! req.session.user){
      console.log('重定向/login', )
      res.redirect('/login')
      return;
  }
  console.log('放行', req.url)
  next()  //调用next()表示放行
});

以上的代码,要放在其他app.use()之前。

创建一个login.html文件

    <form action="/login" method="post">
        用户名:<input type="text" name="username" id="username">
        <br/>
        密码:<input type="password" name="password" id="password">
        <br/>
        <input type="submit" value="登录">
    </form>

6、使用post登录

修改app.js文件,在权限控制代码后面增加以下内容:

// 引入模块
const bodyparser = require('body-parser')
// 使用中间件,处理application/x-www-form-urlencoded
app.use(bodyparser.urlencoded({extended:false}))

接下来就可以使用req.body访问post请求的参数了。

如果登录成功,就可以使用

req.session.user = ....

设置会话。

销毁会话,使用

req.session.destroy((err)=>{console.print('销毁')})

如果已经配置了

app.use((req, res, next)=>{
  res.locals.session = req.session
  next()
});

那么,在页面访问会话中的内容,使用

{{session.user}}

7、退出操作

router.get('/logout', function(req, res){
  req.session.destroy((err)=>{});
  res.redirect('/');
});

8、编辑操作

在页面使用

<a href="/edit?id={{$value.id}}">修改</a>

在index.js中,增加函数

router.get('/edit', function(req, res){
  let id = req.query.id;
  db(`select * from stu where id=${id}`, rows=>{
    res.render('edit.html', {stu:rows[0]});
  });
});

保存修改的函数

router.post('/edit', function(req, res){
  let id = req.body.id, name = req.body.name, age=req.body.age;
  db(`update stu set name='${name}', age=${age} where id=${id}`, rows=>{
    res.redirect('/')
  });
});

9、添加、删除操作

参考编辑操作,略

10、上传头像

上传文件的表单,使用post提交,要有属性enctype="multipart/form-data"

    <form action="/add" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="name" id="">
        <br>
        密码:<input type="text" name="pwd" id="">
        <br>
        年龄:<input type="text" name="age" id="">
        <br>
        照片:<input type="file" name="picture" id="">
        <br>
        <input type="submit" value="添加">
    </form>

nodejs中使用multer模块,先安装npm install multer。然后在index.js中增加以下内容

// 导入模块
var multer  = require('multer')
// 创建multer实例,指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});
// 上传目录
var UPLOAD_DIR = "./upload/";
// 文件夹必须存在,否则报错
if(!fs.existsSync(UPLOAD_DIR)){
  fs.mkdirSync(UPLOAD_DIR);
}

修改一下以前的添加方法

router.post('/add', upload.any(), function(req, res){
  let name = req.body.name, pwd = req.body.pwd, age = req.body.age;

  // 上传的文件都保存在req.files,每个文件都是一个对象。
  // 对象的path是上传后的路径,名称是随机生成的。
  // 同步读取文件
  let data = fs.readFileSync(req.files[0].path);
  // 对象的originalname是原始文件名
  let dst_file =  UPLOAD_DIR + req.files[0].originalname;
  // 同步写入文件
  fs.writeFileSync(dst_file, data);  
  
  let sql = `insert into stu(name,pwd,age,image)values('${name}', '${pwd}', ${age}, '${dst_file}')`
  db(sql, rows=>{
    res.redirect('/')
  });
});

11、下载头像

只需要使用res.download()即可。

router.get('/download', function(req, res){
  let id = req.query.id;
  db(`select * from stu where id=${id}`, (rows)=>{
    res.download(rows[0].image);
  });
});

12、验证码

使用svg-captcha模块,安装npm install svg-captcha

下面是生成验证码的代码。要注意url使用/static开头,防止权限过滤掉。

// 导入模块
const svgCaptcha = require('svg-captcha')
// 页面:获取验证码
router.get('/static/captcha', (req, res)=>{
    let captch = svgCaptcha.create({
        color: true,    // 彩色
        width:100,      // 宽度
        height:60,      // 高度
        fontSize:48,    // 字体大小
        size:4,         // 验证码个数,这里的captch.text是4个字母
        noise:3,        // 干扰线条
        ignoreChars:'0o1ilI'    // 不包括的字符
    });
    // 保存到session中,登录时取出校验
    req.session.captcha = captch.text.toLocaleLowerCase();
    // 防止使用模板:指定输出类型
    res.type('html')
    // 这里的captch.data 是一个svg的html内容。指定输出内容
    res.end(captch.data)
});

下面是页面中使用验证码的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
	var captcha = function(){
		$.get('/static/captcha', data=>{
			$('#captchaImg').html(data)
		})
	}
	$(function(){
		captcha()
	   $('#captchaImg').click(()=>{
		captcha()
	   })
	});
</script>
</head>
<body>
   
    <form action="/login" method="post">
        用户名:<input type="text" name="username" id="username" value="张三">
        <br/>
        密码:<input type="password" name="password" id="password" value="admin">
        <br/>
        验证码:<input type="text" name="captchaCode" id="captchaCode"><span id="captchaImg"></span>
        <br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

登录时,进行验证

router.post('/login', (req, res)=>{
  let name = req.body.username, pwd = req.body.password, captchaCode = req.body.captchaCode;
  // 判断验证码
  if (req.session.captcha != captchaCode.toLocaleLowerCase()){
    res.render('login.html', {error:'验证码错误'});
    return;
  }
  let sql = `select * from stu where name='${name}' and pwd='${pwd}'`
  
  db(sql, rows=>{
    if(rows){
      req.session.user = rows[0]
      res.redirect('/')
    }else{
      res.render('login.html')
    }
  });
});

七、Mongoose操作

使用npm install mongoose -S,按照mongoose

7.1 创建数据库连接

const mongoose = require('mongoose')

mongoose.connect('mongodb://localhost:27017/test44', {
    useNewUrlParser:true,
    useCreateIndex:true,
    useFindAndModify:true    
}).then(()=>console.log('数据库链接正常'))
.catch(err=>console.error(`数据库链接出错 `, err))

该文件可以在main.js中引入,与模型无关。这样,一次性加载数据库连接。

7.2 创建模型

下面的模型中,使用了ObjectId类型,默认是null。

const mongoose = require("mongoose");

const Funcpoint = mongoose.model(
  "Funcpoint",
  new mongoose.Schema({
    pid: {
      type: mongoose.ObjectId,
      ref: "Funcpoint",
      default: null,
    },
    label: {
      type: String,
      required: true,
    }
   })
 )

模型有很多参数,如下

const User = mongoose.model(
  "User",
  new mongoose.Schema({
    username: { 
      type: String, 
      required: true, 
      unique: true  // 唯一性
    },
    password: {
      type: String,
      required: true,
      set: (val) => require("bcrypt").hashSync(val, 10),    //对密码加密
    },
  })
);

7.3 常见操作

// 保存对象,如果没有id则是插入,如果有id则是更新
const user = new User({...})
user.save()

// 还有一种方法
User.create({....})

模糊查询是下面的操作

  router.get('/', async(req, res)=>{
    let params = {};
    if (req.query) {
      let regexp = new RegExp(req.query.label, "i");
      params = {
        $or: [{ label: { $regex: regexp } }],
      };
    }

    console.log("菜单查询参数", req.query, params);

    const all = await Funcpoint.find(params);
    const tree = helper.menuTree(all);
    helper.ok(res, { data: tree });
  })

八、一小时搞定权限认证

需要用到的模块express、mongoose、bcrypt、jsonwebtoken

1 数据库部分

const bcrypt = require('bcrypt')
const mongoose = require('mongoose')

mongoose.connect('mongodb://localhost:27017/test441',{
    useNewUrlParser:true,
    useCreateIndex:true,
    useFindAndModify:true
}).then(() => console.log("数据库连接正常"))
  .catch(err => console.error("数据库连接错误", err));


const UserSchema = new mongoose.Schema({
  username: { type: String, required: true, unique:true },
  password: { type: String, required: true, set: (val) => bcrypt.hashSync(val, 10) },
});

const User = mongoose.model("User",UserSchema);

module.exports={User}

2 服务器部分

const jwt = require("jsonwebtoken");
const { User } = require("./models");
const express = require("express");
const app = express();
//req.body读取json数据
app.use(express.json());

// token密钥
const TOKEN_KEY = "2f.-Alkl3w20LKLS)A09S()(*";

// 验证token中间件
const auth = (req, res, next) => {
  const token = req.headers.authorization;
  jwt.verify(token, TOKEN_KEY, (err, payload) => {
    if (err) return res.status(422).json({ msg: "token不正确" });
    // 没问题的话,直接放行
    next();
  });
};

// 首页
app.get("/api", async (req, res) => {
  res.send("测试ok");
});

// 用户列表
app.get("/api/users", async (req, res) => {
  res.json(await User.find());
});

// 注册
app.post("/api/register", async (req, res) => {
  const user = await User.create(req.body);
  res.send(user);
});

// 登录
app.post("/api/login", async (req, res) => {
  // 1 查询用户
  const user = await User.findOne({ username: req.body.username });
  // 2 判断用户是否存在
  if (!user) {
    return res.status(422).json({ msg: "用户名不正确" });
  }
  // 3 验证密码是否正确
  if (!require("bcrypt").compareSync(req.body.password, user.password)) {
    return res.status(422).json({ msg: "密码不正确" });
  }
  // 4 生成token
  const token = jwt.sign({ id: user._id }, TOKEN_KEY, {
    expiresIn: "1h",
  });
  // 5 发送客户端
  res.json({ msg: "登录成功", data: user, token: token });
});

// 刷新token,验证token
app.get("/api/token", auth, async (req, res) => {
  // 1 从请求头获取token
  let token = req.headers.authorization;
  // 2 解析token
  const raw = jwt.verify(token, TOKEN_KEY);
  console.log("取token中的值", raw);
  // 3 从数据库查询用户
  const user = User.findById(raw.id);
  if (!user) {
    return res.status(422).json({ msg: "伪造token" });
  }
  // 4 生成新的token
  const token1 = jwt.sign({ id: user._id }, TOKEN_KEY, {
    expiresIn: "1h",
  });
  // 5 发送到客户端
  res.json({ msg: "刷新成功", token: token1 });
});

// 主页,验证token
app.get("/api/home", auth, async (req, res) => {
  res.send("home");
});

const PORT = 33333;
app.listen(PORT, () => console.log(`the server is running on ${PORT} .....`));

3 使用rest client测试部分

@url = http://localhost:33333/api
@json = Content-Type: application/json


### 首页
get {{url}}

### 全部用户
get {{url}}/users

### 注册
post {{url}}/register
{{json}}

{
    "username":"usre1",
    "password":"123456"
}


### 登录
post {{url}}/login
{{json}}

{
    "username":"usre1",
    "password":"123456"
}

### 刷新token
get {{url}}/token
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNjJjZjhlM2U5Mjc5Mzc1ODhmNmQ3NyIsImlhdCI6MTYwMDMxMjI5NywiZXhwIjoxNjAwMzE1ODk3fQ.Wy-JxUcYMQ-5Cul1eV9Y3HJ3qRM9zVF13XYtM6hY5kM


### 主页
get {{url}}/home
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNjJjZjhlM2U5Mjc5Mzc1ODhmNmQ3NyIsImlhdCI6MTYwMDMxMjI5NywiZXhwIjoxNjAwMzE1ODk3fQ.Wy-JxUcYMQ-5Cul1eV9Y3HJ3qRM9zVF13XYtM6hY5kM

4 集成swagger

按照模块 npm install swagger-jsdoc swagger-ui-express --save

配置代码部分

var path = require("path");
var express = require("express");
var swaggerUi = require("swagger-ui-express");
var swaggerJSDoc = require("swagger-jsdoc");

// 配置 swagger-jsdoc
const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      version: "1.0.0",
      title: "智慧教育",
      description: "api",
      license: {
        name: "吴超",
        url: "http://www.crxy.cn",
      },
    },
    components: {
      schema: {
        Cat: {
          type: "object",
          properties: {
            genus: {
              type: "string",
            },
          },
        },
      },
    },
  },
  // 去哪个路由下收集 swagger 注释
  apis: [path.join(__dirname, "./**/*.js")],
};
var swaggerJson = function (req, res) {
  res.setHeader("Content-Type", "application/json");
  res.send(swaggerSpec);
};
const swaggerSpec = swaggerJSDoc(options);

var swaggerInstall = function (app) {
  if (!app) {
    app = express();
  }
  // 开放相关接口,
  app.get("/swagger.json", swaggerJson);
  // 使用 swaggerSpec 生成 swagger 文档页面,并开放在指定路由
  app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
};
module.exports = swaggerInstall;

在主类中,使用require('./api/swagger-api')(app)引入模块。

这样,通过http://ip:port/api-docs就能看到了。

最关键的是注释的书写,格式如下

    /**
     * @swagger
     * /api/res/  列表:
     *  get:
     *    tags:
     *      - sys/res
     */

https://www.bilibili.com/video/BV1Yt4y1Q7yb?from=search&seid=12652331421225567608

九、 vuejs框架

1、基础入门

1.1、前世后生

Vue是一套用于构建用户界面的渐进式js框架,发布于2014年。

Vue被设计为可以自底向上逐层应用。

Vue的核心库只关注视图层。

最大优点是易于上手、还有丰富的生态圈。

原生js——>jquery类库——>Vue.js/Angular.js/React.js

虚拟dom、双向绑定、整套解决方案、前后端分离

1.2、基础入门

三部曲【1】引入js库【2】指定dom id【3】vuejs渲染

<!--第一步,引入js库-->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<body>
    <!--第二步,创建dom id-->
    <div id="app">{{msg}}</div>
</body>
<!--第三步,渲染-->
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            msg:'hello world'
        }
    })
</script>

还有更惊艳的,可以实现输入框与显示的同步。

<!--第一步,引入js库-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<body>
    <!--第二步,创建dom id-->
    <div id="app">
        {{msg}}
        <!--只增加了下面一行,必须放在app的元素内-->
        <input type="text" v-model='msg'>
    </div>
    
</body>
<!--第三步,渲染-->
<script>
    var vm = new Vue({
        el:'#app',
        data:{
            msg:'hello world'
        }
    })
</script>
如果要获取vm中的内容,可以使用vm.$el 或者 vm.$data

1.3、前后端分离

指的是上下层分离

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIAi0TGd-1608279639357)(image-20200723054735539.png)]

1.4、组成

  • vue核心模块(html\css\js操作)
  • axios模块(通讯)
  • router模块(页面跳转)
  • vuex模块(状态管理)
  • ui模块(elementui、ant design for vue)
  • webpack(打包、压缩、加载)

1.5、MVVM

view【dom元素】——ViewModel【这是vuejs的核心部分】——Model【js对象】

VM会监听到视图的变化,并通知数据发生变化;VM能观察到数据的变化,更新视图内容。这就是双向绑定。

如何随时知道dom数据的变化哪?【1】vue通过Object.defineProperty()劫持虚拟dom对应数值的set和get方法【2】angular通过检测input、change等事件。这个负责劫持或者检测的,称为观察者。

如何在数据变化的时候更新dom视图哪?解析指令语言,获取其中的数据模型。当数据模型变化时,通知dom更新。称为编译器。

1.6、vue-cli

安装时,使用如下命令。目前的版本是4.4.6。

npm install -g @vue/cli

使用vue-cli,可以创建、编译、打包项目。

命令vue init 是vue-cli2.0的语法,新的语法是vue create xxx

vue create hello-vue

// 也可以使用ui
vue ui

// 安装插件,使用
vue install eslint

// 使用服务
vue run start

还需要手工在根目录创建一个配置文件vue.config.js,在里面添加如下内容

module.exports = {
  devServer: {
    port: 33333,
    open: true
  }
}

表示指明端口,并且自动打开浏览器。

2、基本语法

2.1 输出内容:插值表达式

插值表达式是用于把vue对象中的属性和方法输出。

{{ msg }}	// 基本类型
{{ [0,1,2,3,4][0] }}	//数组
{{ {'name':'张三'}.name }}	//对象
{{ sayHi() }}	//方法

<script>
    new Vue({
        el:'#app',
        data:{
            msg:'hello world'
        },
        methods: {
            sayHi:()=>window.alert('aaaaa')
        },
    })
</script>

2.2 输出内容:v-html和v-text

使用v-html后,内部会被html渲染。

<span v-html='html_1'></span>
<span v-text='html_1'></span>
<script>
new Vue({
    el:'#app',
    data:{
        html_1:'<a href="#">我是超链接</a>'
    },
    methods: {
        
    },
})
</script>

上面代码渲染后,就是下面的样子。只是v-html会渲染样式,v-text不会渲染。

<span><a href="#">我是超链接</a></span>

2.3 输出一次 v-once

只取一次值,后续msg的改变,不会更新视图。

<div v-once>{{msg}}</div>

2.4 显示或隐藏 v-show

显示还是隐藏,接收逻辑值。

<span v-show="hidden">显示</span>

当是false时,会替换为

<span style="display: none;">显示</span>

2.5 属性值单向绑定v-bind

用在属性前面,可以绑定属性值。单向绑定。

<a v-bind:href="url">超链接</a>
<script>
    new Vue({
        el:'#app',
        data:{
            msg:'hello world',
            url:'http://www.baidu.com'
        },
    })
</script>
// <a v-bind:href="url">点击这里</a>  可以简写   <a :href="url">点击这里</a>

动态参数<a :[key]="url"> ... </a>

​ 在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写。

2.6 输入值双向绑定v-model

绑定input元素。当input值改变时,vue数据也会改变;当vue数据改变时,input值也会改变。双向绑定。

<input type="text" v-model="msg">

2.7 事件绑定v-on

绑定input元素的事件,函数的参数是事件e,e.data获取当前更新的值,e.target.value获取当前最新值。

<input type="text"  v-on:input='changeValue'>

methods:  {changeValue:(e)=>console.log(e.data, e.target.value)},

也可以传递参数

<button v-on:click='click1(12)'>点击</button>
methods: {click1:(v)=>console.log(v)},

上面的写法太复杂,一般会简写做==@input、@click==

还可以传递事件

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

可以在方法中,使用this访问data或者methods中属性或者方法

    new Vue({
        el:'#app',
        data:{
            msg:'hello world'
        },
        methods: {
            sayHi:()=>window.alert('aaaaa'),
            changeValue:(e)=>console.log(e.data, e.target.value),
            
            // click1是普通函数定义,可以使用this访问到msg
            click1:function(v){console.log(this.msg);},
            
            // click2是箭头函数定义,这里的this不能到vue实例
            click2:(v)=>console.log(this.msg)
        },
    })

动态参数<a @[event]="doSomething"> ... </a>

2.8 if判断

三个原语:v-if、v-else-if、v-else

<div id="app">
    <div v-if="type === 'A'">
      A
    </div>
    <div v-else-if="type === 'B'">
      B
    </div>
    <div v-else-if="type === 'C'">
      C
    </div>
    <div v-else>
      Not A/B/C
    </div>
</div>

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,这么做除了使 Vue 变得非常快。但是带来一个问题:如果复用了相同的组件,那么组件的内容不会被更新,因为vue认为是同一个组件,不需要再次渲染。怎么办?只需添加一个具有唯一值的 key attribute 即可。

2.9 for循环

下面是遍历一个数组,注意索引i是在后面

<li v-for="(item, i) in items">
索引:{{i}}    值:{{item}}
</li>

下面是遍历一个对象中的属性和值

<li v-for="(value, key, index) in object">
    索引:{{ index }}   属性:{{ key }}    值:{{ value }}
</li>

下面是遍历一个整数

<li v-for="n in 10">
     {{ n }}
</li>

使用for循环渲染组件的时候,必须使用key。

2.10 计算属性

定义的是一个方法,但是用法上是一个属性。使用计算属性的目的是为了解决表达式中不可能完成复杂的逻辑。

下面的代码中有很多操作,显得很臃肿

<div id="app">
  {{ msg.split('').reverse().join('') }}
</div>

下面是定义一个计算属性,并使用它。

var vm = new Vue({
  el: '#app',
  data: {
    msg: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})
<div id="app">
  <p>Original message: "{{ msg }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

计算属性和方法的区别:

计算属性是个方法,第一次执行后缓存结构。以后执行与否,看依赖的msg是否有变化。如果以来的msg没有变化,直接返回结果。

2.11 监听器watch

数据之间有依赖关系,比如下面的fullName是由firstName和lastName组成的。当firstName或者lastName变化的时候,fullName会自动变化。

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            firstName: 'chao',
            lastName: 'wu',
            fullName: 'wu chao'
        },
        watch: {
            firstName: function (val) {
                this.fullName = val + ' ' + this.lastName
            },
            lastName: function (val) {
                this.fullName = this.firstName + ' ' + val
            }
        }
    });
</script>

这种情况下,也可以使用计算属性fullName,定义其读写方法。

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

从代码量和书写风格上比较,在这种监控数值变化的情况下,watch更合适。比如省市县联动功能的实现。

2.12 事件修饰符

<!-- a标签点击后会实现页面跳转,这里阻止跳转,只执行doThis方法 -->
<a v-on:click.stop="doThis"></a>

<!-- submit方法默认会提交表单,这里阻止提交,只执行onSubmit方法 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

2.13 样式绑定

当isActive的是真值时,class的值是“static active”;否则是“static”。

<div  class="static" v-bind:class="{ active: isActive }"></div>

也可以绑定class对象

<div v-bind:class="classObject"></div>
data: {
  classObject: {
    active: true
  }
}

也可以传递数组

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
// 渲染为
<div class="active text-danger"></div>

2.14 表单处理

表单输入值使用v-model,可以实现双向绑定。

普通文本框

<input type="text" v-model="text1">

多行文本框

<textarea v-model="text2" cols="30" rows="10"></textarea>

多选框

<span v-for="(item,i) in favors">
    <input type="checkbox" :id="i" name="aa" :value="item" v-model="checked">{{item}}
</span>

单选框

<span v-for="(item,i) in favors">
     <input type="radio" :id="i"  :value="item" v-model="checked">{{item}}
</span>

下拉框

<select   v-model="checkedFavor">
    <option disabled value="请选择..."></option>
    <option v-for="(item,i) in favors" :value="item">{{item}}</option>
</select>
数据同步的修饰符
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">

<!-- 把用户输入自动转为number类型,存到age中 -->
<input v-model.number="age" type="number">

<!-- 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">

2.15 自定义指令

下面是一个自定义指令的例子

<div id="app">
    <p>页面载入时,input 元素自动获取焦点:</p>
    <input v-focus>
</div>
 
<script>
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
  // 当绑定元素插入到 DOM 中。
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
// 创建根实例
new Vue({
  el: '#app'
})
</script>

自定义指令使用Vue.directive(‘指令名’, 指令定义)。

在指令定义中,主要是实现钩子函数。

  • bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
  • unbind: 只调用一次, 指令与元素解绑时调用。

钩子函数的参数有:

  • el: 指令所绑定的元素,可以用来直接操作 DOM 。

  • binding: 一个对象,包含以下属性:

    • name: 指令名,不包括 v- 前缀。
    • value: 指令的绑定值, 例如: v-my-directive=“1 + 1”, value 的值是 2。
    • oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression: 绑定值的表达式或变量名。 例如 v-my-directive=“1 + 1” , expression 的值是 “1 + 1”。
    • arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 “foo”。
    • modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
  • vnode: Vue 编译生成的虚拟节点。

  • oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

很多情况下,只关注bind和update钩子函数执行情况,可以缩写如下

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

2.16 自定义事件

自定义的事件名不需要大小写转换。

this.$emit('myEvent')

2.17 模块

一个vue文件,可以作为一个模块。那么,一个模块通常会向外暴露很多可供外部使用的变量和方法。常使用下面的形式

<script>
export default {
  name: 'hello',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  },
  methods: {
          login: function() {
              console.log(this.username,this.password);
          }
        }
}
</script>

2.18 生命周期

  • created 数据已经初始化,能访问vm中的数据。比如可以初始化一些数据。
  • mounted 页面dom已经有了,可以访问dom元素了。比如可以加载外部的js控件进入vue体系中
var app = new Vue({
  el: "#app",
  data: {
    form: {
      data_range: getSessionItem("data_range"),
      datafiles: [],
      datafileId: getSessionItem("datafileId"),
    },
    option: {},
  },
  created() {},
  mounted() {
    igraph.i18n.setLanguage("chs");
    this.app = new igraph.GraphNavigator(document.getElementById("graphArea"));
  }
 })

3、组件

组件就是单独的一个Vue实例。使用==Vue.component(tagName, options)==定义。

下面是定义组件

<script>
    // 注册
    Vue.component('tpl1', {
      template: '<h1>自定义组件!</h1>'
    })
    // 创建根实例
    new Vue({
      el: '#app'
    })
</script>

下面是使用组件

<div id="app">
	<tpl1></tpl1>
</div>

3.1 全局组件和局部组件

刚才定义的就是全局组件,局部组件是限定在某个vue实例中使用。

<div id="app">
    <tpl2></tpl2>
</div>
 
<script>
var Child = {
  template: '<h1>自定义组件!</h1>'
}
 
// 创建根实例
new Vue({
  el: '#app',
  components: {
    'tpl2': Child
  }
})
</script>

组件内部只能有一个根元素。

3.2 传值:父传子

子组件使用props属性,定义接收自父组件的参数,可以有多个参数。

Vue.component('grid-position', {
	props: ['option'],
	template: `
		<el-popover placement="bottom" width="400" trigger="click">
		  <el-form-item label="距上">
		      <el-slider v-model.number="option.grid.top"></el-slider>
		  </el-form-item>
		  <el-form-item label="距右">
		      <el-slider v-model.number="option.grid.right"></el-slider>
		  </el-form-item>
		  <el-form-item label="距下">
		      <el-slider v-model.number="option.grid.bottom"></el-slider>
		  </el-form-item>
		  <el-form-item label="距左">
		      <el-slider v-model.number="option.grid.left"></el-slider>
		  </el-form-item>
		  <el-button slot="reference">位置</el-button>
		</el-popover>
		`
})

父组件在调用的时候,传递参数,可以是静态的固定值,也可以是动态的绑定也。这里传递的参数,可以是一个复杂对象。

<grid-position v-bind:option="option" ></grid-position>

props中的属性可以指定类型

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

也可以指定默认值

  props: {
    formItems: {
      type: Array,
      required: true,
      default: () => []
    },
    formModel: {
      type: Object,
      required: true,
      default: () => {}
    }
  }

传递一个对象的所有属性

<blog-post v-bind="post"></blog-post>
等价于下面的写法
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title">
</blog-post>

props中如果定义属性是postTitle这种驼峰写法,那么在父组件传入的时候,使用post-title写法。

3.3 父调用子的方法,使用refs

子组件必须指定属性ref

<childtpl ref="childtpl"/>

父组件就可以使用this.$ref.childtpl 获得该子组件,就可以访问该子组件中的数据、方法了。

3.4 子调用父的方法,使用emit

需要使用自定义事件完成子组件向父组件传递数据。

在子组件中,调用$emit发射事件,包括事件名、参数

this.$emit('add', 1, 2)

父组件在使用子组件的时候,必须注册该事件,然后绑定父组件中的padd方法

<button-counter v-on:add="padd"></button-counter>

new Vue({
  el: '#app',
  data: {
    total: 0
  },
  methods: {
    padd: function (a, b) {
      this.total = a+b
    }
  }
})

这种方法,其实挺麻烦的。还有一种简单的方法,就是注册到Vue原型中一个实例,作为父子组件共享的对象

Vue.prototype.$chart = new EChart();

这样,就可以在父子组件都,都可以使用this.$chart访问其中的属性和方法了。

3.5 双向传值:使用sync修饰符

当子组件修改父组件中的属性值时,就可以使用sync修饰符。

父组件调用子组件传值时,使用sync
<comp :foo.sync="bar"></comp>
子组件要想改变父组件的值,需要使用$emit发射update事件
this.$emit('update:foo', newValue)

3.6 双向传值:v-model

v-model指令是v-bind和v-on的组合。

<input type='text' v-model='msg'>
// 相当于
<input type='text' :value=msg @input='msg =$event.target.value'>

在模块中,可以使用v-model指令,允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event。

在正常的情况下,父组件给子组件传值,子组件使用props属性接收。如果子组件要改变父组件的值,需要使用$emit发生事件给父组件,父组件响应子组件的方法调用。这样,在父组件中,多了响应函数。使用v-model就可以在父组件中不写响应函数,并且实现父子间的双向传值。

下面是父组件调用

<div id="app">
     <input-price v-model="price"></input-price>
</div>

下面是子组件实现,可以看出在子组件中进行了@input绑定。

Vue.component('input-price', {
    // 2、当有数据输入时触发了该组件的input事件
    template: '<input :value="value" @input="updateVal($event.target.value)" type="text">',
    // 1、将父组件的value值通过props传递给子组件
    props: ["value"],
    methods: {
         updateVal: function(val) {
            // 3、手动触发父组件的input事件并将值传给父组件
            this.$emit('input', val);
         }
     }
});
var app = new Vue({
     el: '#app',
     data: {
         price: ''
     },
     methods: {
          onInput: function(val) {
               this.price = val;
          }
      }
 });

3.7 使用$parent$children

3.8 任意组件间:使用事件总线

原理是使用公共的js文件中的公共vue对象传值。

创建公共js文件bus.js

import Vue from 'vue'
export default new Vue;

在需要发送和接收消息的地方引入

import bus from './bus.js'

// 发送消息
bus.$emit('msg', val)
// 接收消息
bus.$on('msg', val=>console.log(val))

3.9 插槽

假如父组件需要在子组件内放一些DOM,那么这些DOM是显示或者隐藏,在哪个地方显示,怎么显示,需要slot分发负责。

无名插槽
有名插槽
<div class="" id="app">
    <children>
      <span slot="first" @click="test()">12345</span>
      <span slot="third">56789</span>
    </children>
  </div>
  <script type="text/javascript">
    new Vue({
      el: "#app",
      data: {
      },
      components: {
        children: { //这个无返回值,不会继续派发
          template: "<button><slot name='first'></slot>为了明确作用范围<slot name='third'></slot>所以使用button标签</button>"
        }
      },
      methods: {
        test: function() {
          console.log("我是first点击打印的内容");
        }
      }
    })
  </script>
插槽作用域

3.10 动态组件

动态组件指的是把几个组件,放在一个挂载点下,根据父组件的变量决定显示哪个。

1、使用import导入组件,可以获取到组件

var name = 'system';
var myComponent =() => import('../components/' + name + '.vue');
var route={
  name:name,
  component:myComponent
}

2、使用import导入组件,直接将组件赋值给componet

var name = 'system';
var route={
  name:name,
  component :() => import('../components/' + name + '.vue');
}

3、使用require 导入组件,可以获取到组件

var name = 'system';
var myComponent = resolve => require.ensure([], () => resolve(require('../components/' + name + '.vue')));
var route={
  name:name,
  component:myComponent
}

4、使用require 导入组件,直接将组件赋值给componet

var name = 'system';
var route={
  name:name,
  component(resolve) {
    require(['../components/' + name + '.vue'], resolve)
  }
}

在挂载点使用component标签,然后使用v-bind:is="组件名",会自动去找匹配的组件名;如果没有,则不显示。

改变挂在的组件,只需要修改is指令的值即可。

<div id="app">
    <button @click="toshow">点击让子组件显示</button>
    <component v-bind:is="which_to_show"></component>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            which_to_show: "first"
        },
        methods: {
            toshow: function () {   //切换组件显示
                var arr = ["first", "second", "third", ""];
                var index = arr.indexOf(this.which_to_show);
                if (index < 3) {
                    this.which_to_show = arr[index + 1];
                } else {
                    this.which_to_show = arr[0];
                }
            }
        },
        components: {
            first: { //第一个子组件
                template: "<div>这里是子组件1</div>"
            },
            second: { //第二个子组件
                template: "<div>这里是子组件2</div>"
            },
            third: { //第三个子组件
                template: "<div>这里是子组件3</div>"
            },
        }
    });
</script>

当切换时,不显示的组件会被直接移除,再次显示时会重新渲染。为了避免重复渲染,可以使用keep-alive属性。

<div id="app">
    <button @click="toshow">点击让子组件显示</button>
    <component v-bind:is="which_to_show"  keep-alive></component>
</div>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            which_to_show: "first"
        },
        methods: {
            toshow: function () {   //切换组件显示
                var arr = ["first", "second", "third", ""];
                var index = arr.indexOf(this.which_to_show);
                if (index < 3) {
                    this.which_to_show = arr[index + 1];
                } else {
                    this.which_to_show = arr[0];
                }
            }
        },
        components: {
            first: { //第一个子组件
                template: "<div>这里是子组件1</div>"
            },
            second: { //第二个子组件
                template: "<div>这里是子组件2</div>"
            },
            third: { //第三个子组件
                template: "<div>这里是子组件3</div>"
            },
        }
    });
</script>

3.11 异步组件

按需加载组件

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

4、零散知识点

4.1 createElement函数

4.2 render函数

使用render函数我们可以用js语言来构建Dom

4.3 插件

4.4 自定义过滤器

7、异步请求模块axios

axios时一个基于Promise用于浏览器和nodejs的http客户端。

7.1 axios的特点

  • 从浏览器中创建XMLHttpRequest

  • 支持Promise API

  • 客户端支持防止CSRF

  • 提供了一些并发请求的接口

  • 从nodejs创建http请求

  • 拦截请求和响应

  • 转换请求和响应数据

  • 取消请求

  • 自动转换json数据

7.2 axios入门

引入axios模块

# 1、安装
npm install axios --save
# 2、在某个vue文件中配置引入
import axios from 'axios'

7.3 更多例子

// get请求,可以通过 params 对象传递参数,也可以省略params
axios.get('/user', {
	params: {
		ID: 12345
	},
	headers:{}
}).then(function (response) {
	console.log(response);
});

// post请求
axios.post('/user', {
	firstName: 'Fred',
	lastName: 'Flintstone'
    },
    params:{}
).then(function (response) {
	console.log(response);
});

除此之外,还有更多方法,如

  • axios#request(config)
  • axios#get(url[, config])
  • axios#delete(url[, config])
  • axios#head(url[, config])
  • axios#options(url[, config])
  • axios#post(url[, data[, config]])
  • axios#put(url[, data[, config]])
  • axios#patch(url[, data[, config]])

7.4 封装

建立文件夹api,创建文件index.js

import axios from 'axios';
  // 全局配置
axios.defaults.baseURL = 'https://api.example.com';

// 自定义配置
const instance = axios.create({
  baseURL: 'https://..../',
  timeout: 5000
}); 


// 添加请求拦截器
instance.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });


export function get(url, params){
  return instance.get(url, {params});
}

export function post(url, data){
  return instance.post(url, data);
}

7.5 拦截器

// 自定义实例,这样添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

7.7 并发请求

// 同时发送一组请求
function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread((acct, perms) => {
    // 两个请求都完成后
}));

axios.all方法接受一个数组作为参数,数组中的每个元素都是一个请求,返回一个promise对象,当数组中所有请求均已完成时,执行then方法。在then方法中执行了 axios.spread 方法。该方法是接收一个函数作为参数,返回一个新的函数。接收的参数函数的参数是axios.all方法中每个请求返回的响应。

8、 路由模块vue-router

参考 https://www.jianshu.com/p/4c5c99abb864

vue-router是在spa中建立url与页面的对应关系。

路由有两种模式,一种是在url上带上链接信息hash模式(即#hash),一种是通过浏览器的状态控制history模式。

8.1 路由方法

方式1:直接修改地址栏

方式2:编程式导航:this.$router.push(‘路由地址’)

方式3:声明式导航:

8.2 路由的使用方式

  1. 下载npm install vue-router

  2. 在main.js中引入 import VueRouter from ‘vue-router’

  3. 安装插件Vue.use(VueRouter)

  4. 创建路有对象并配置路由规则

    let router = new VueRouter({routes:[
      {path:'/home', component:()=>import('@/home.vue')}
    ]})
    
  5. 将其路由对象传递给Vue实例

  6. 在app.vue中留坑

8.3 动态路由

指的是路由url上带有用户id或者查询参数之类的动态信息。

那么,配置的使用,使用如下形式

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: '/user/:id', component: User }
  ]
})

组件中使用this.$route.params.获取动态参数

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

需要注意的是,动态路由会让组件重用,不是新建组件。所以,组件的生命周期函数不会调用,为了再次调用生命周期函数中的方法,需要监控路由的变化,使用beforeRouteUpdate方法

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

8.4 组件内的导航守卫

可以监控url的来源变化

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

8.5 嵌套路由

用于在一个页面内显示一个新内容。

因此,需要在原页面的路由中增加children子元素

  {
    path: '/user',
    component: User,
    children: [
      {
        path: 'add',
        component: AddUser
      }
    ]
  },

在user页面增加,用于显示嵌套的页面。

可以使用动态路由的方式,指向子页面

this.$router.push({ path: '/user/add' })

8.6 编程式导航

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

8.7 命名路由

使用命名路由,可以在一个地方配置路由路径。

8.8 命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

8.9 重定向和别名

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})
// 下面是重定向到一个有名的路由
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

8.10 路由组件传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

使用 props 将组件和路由解耦:

8.11 导航守卫

router.beforeEach((to, from, next) => {
  // console.log("from", from);
  // 获取token
  const token = sessionStorage.getItem("token");
  // 如果是登录或者有token,则通过
  if (to.path === "/login" || token) return next();
  console.error("未授权用户,请登录");
  next("/login");
});

9、 全局状态管理vuex

9.1 基本安装

// 1、安装
npm install vuex --save
// 2、在main.js中导入
import Vuex from 'fvuex'
Vue.use(Vuex)
// 3、创建store对象
const store = new Vuex.Store({ state:{count:0} })
// 4、将store对象挂载到vue实例中
new Vue({...  store ....})

9.2 核心概念

9.2.1 state

State提供唯一的公共数据源。

// 定义全局变量
export default new Vuex.Store({
  state:{ count:0 }
})

// 访问state中数据的第1种方式
this.$store.state.全局数据名称
// 访问state中数据的第2种方式
import {mapState} from 'vuex'
computed:{
  ...mapState(['count'])	// 将全局数据,映射为当前组件的计算属性
}
9.2.2 mutation

Mutation用于改变store中的数据。这里面不能写延迟等异步方法。

new Vuex.Store({
  state: ...,
  // 在mutations的属性值中定义各种改变state的函数
  mutations:{
    add(state, num){
      state.count += num;
    }
  }
});

执行mutations函数的方法

// 第1种方法,在其他地方调用commit出发上面定义的函数,可以传递参数
this.$store.commit('add', 3)
// 第2种方法,使用mapMutations
import {mapMutations} from 'vuex';

methods:{
  ...mapMutations(['add'])  把全局函数映射为组件内部的函数
}
9.2.3 Action

专门用于处理异步的任务。需要在action的函数中调用mutation函数改变数据。

new Vuex.Store({
  mutations:{
    add(state, num){
      state.count+=num
    }
  },
  actions:{
    addAsync(context, num){
      setTimeout({
        context.commit('add', num)
      }, 1000)
    }
  }
})

触发action动作

// 第1种方法
this.$store.dispatch('addAsync', 5)

// 第2种方法
import {mapActions} from 'vuex'
methods:{
  ...mapActions({'addAsync'})
}
9.2.4 Getter

用于对store中的数据进行加工,产生新的数据,不会改变原始数据。

state:{
  count:0
},
getters:{
  showNum:state=>{
    return '新数字'+state.count
  }
}

使用getters

// 第1种方式
this.$store.getters.名称
// 第2种方式
import {mapGetters} from 'vuex'
computed:{
  ...mapGetters(['showNum'])
}
9.2.5 模块

为了避免出现同名变量或者函数冲突的情况,可以使用命名空间,即模块。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const tab = {
  namespaced: true,
  state: {
    count: 100
  },
  mutations: {
    add(state, n) {
      state.count += n
    }
  },
  actions: {}
}

export default new Vuex.Store({
  modules: { tab }
})

引用时,在state后面添加模块名称,即使用$store.state.tab.count形式。

引用时,在模块中的方法,使用this.$store.commit('nav/handleNav1', key) 引用。

12、 脚手架vue cli

11、打包工具webpack

webpack是一个前端资源的加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

12、vue-grid-layout

# 1、安装
npm install vue-grid-layout --save

# 2、使用组件,两个核心组件
import { GridLayout,GridItem } from 'vue-grid-layout';

export default {
    components: {
        GridLayout, GridItem
    }
}

有两个核心类GridLayoutGridItem

12.1 GridLayout属性

属性名称类型必填默认值注释
layout.syncArraytrue布局位置。数组中每个元素是GridItem。每个元素必须有i、x、y、w、h属性。
colNumNumberfalse12表格有多少列
rowHeightNumberfalse150一行的高度
isDraggableBooleanfalsetrue是否允许拖拽
isResizableBooleanfalsetrue是否允许缩放
isMirroredBooleanfalsefalse是否允许替换翻转
verticalCompactBooleanfalsetrue是否允许垂直压缩
marginArrayfalse[10,10]边距,必须有2个元素,第1个数字表示水平边距,第2个数字表示垂直边距。单位是像素。
useCssTransformsBooleanfalsetrue标识是否使用CSS属性transition-property: transform;定位,否则是position定位

12.2 GridLayout事件

  • layoutCreatedEvent 对应vue生命周期的created

        layoutCreatedEvent: function(newLayout){
          console.log("Created layout: ", newLayout)
        }
    
  • layoutBeforeMountEvent 对应vue生命周期的beforeMount

        layoutBeforeMountEvent: function(newLayout){
          console.log("beforeMount layout: ", newLayout)
        }
    
  • layoutMountedEvent 对应vue生命周期的mounted

        layoutMountedEvent: function(newLayout){
          console.log("Mounted layout: ", newLayout)
        }
    
  • layoutReadyEvent 当完成mount中的所有操作时生成的试卷

        layoutReadyEvent: function(newLayout){
          console.log("Ready layout: ", newLayout)
        }
    
  • layoutUpdatedEvent 更新事件(布局更新或上方元素的位置重新计算)

        layoutUpdatedEvent: function(newLayout){
          console.log("Updated layout: ", newLayout)
        }
    

12.3 GridItem属性

属性名称类型必填注释
iStringtrue元素的唯一ID
xNumbertrue位于第几列
yNumbertrue位于第几行
wNumbertrue初始宽度,是colWith的倍数
hNumbertrue初始高度,是rowHeight的倍数

12.4 GridItem事件

  • resizeEvent 调整大小时的事件

  • moveEvent 移动后的事件

        moveEvent: function(i, newX, newY){
            console.log("MOVE i=" + i + ", X=" + newX + ", Y=" + newY);
        },
    
  • resizedEvent 调整大小后的事件

        resizeEvent: function(i, newH, newW, newHPx, newWPx){
            console.log("RESIZE i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx);
        },
    
  • containerResizedEvent

  • movedEvent

        movedEvent: function(i, newX, newY){
            console.log("MOVED i=" + i + ", X=" + newX + ", Y=" + newY);
        },
    
  • resizedEvent

        /**
         * 
         * @param i the item id/index
         * @param newH new height in grid rows 
         * @param newW new width in grid columns
         * @param newHPx new height in pixels
         * @param newWPx new width in pixels
         * 
         */
        resizedEvent: function(i, newH, newW, newHPx, newWPx){
            console.log("RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx);
        },
    
  • containerResizedEvent

        /**
         * 
         * @param i the item id/index
         * @param newH new height in grid rows 
         * @param newW new width in grid columns
         * @param newHPx new height in pixels
         * @param newWPx new width in pixels
         * 
         */
        containerResizedEvent: function(i, newH, newW, newHPx, newWPx){
            console.log("CONTAINER RESIZED i=" + i + ", H=" + newH + ", W=" + newW + ", H(px)=" + newHPx + ", W(px)=" + newWPx);
        },
    

13、生成并验证token

使用token认证的方式:

  1. 客户端不需要持有密钥,由服务端通过密钥生成Token。

  2. 客户端登录时通过账号和密码到服务端进行认证,认证通过后,服务端通过持有的密钥生成Token,Token中一般包含失效时长和用户唯一标识,如用户ID,服务端返回Token给客户端。

  3. 客户端保存服务端返回的Token。

  4. 客户端进行业务请求时在Head的Authorization字段里面放置Token,如:Authorization: Bearer Token

  5. 服务端对请求的Token进行校验,并通过Redis查找Token是否存在,主要是为了解决用户注销,但Token还在时效内的问题,如果Token在Redis中存在,则说明用户已注销;如果Token不存在,则校验通过。

  6. 服务端可以通过从Token取得的用户唯一标识进行相关权限的校验,并把此用户标识赋予到请求参数中,业务可通过此用户标识进行业务处理。

  7. 用户注销时,服务端需要把还在时效内的Token保存到Redis中,并设置正确的失效时长。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QK8czPDw-1608279639361)(568153-20180610221254297-2132347724.png)]

// 1、安装
`npm install jsonwebtoken `

// 2、导入
const jwt = require('jsonwebtoken')

// 3、服务端登陆时生成token
let token = jwt.sign(
  { id: data.id, username: data.name }, 	#payload,一般是登录用户的信息
  '我是密钥', 	#密钥
  { expiresIn: '1h' }	#过期时间
)

// 4、客户端再次访问时,服务端获取请求头中的token并验证
let token = req.headers.authorization;
if (token) {
jwt.verify(token, '我是密钥', (err, decoded) => {
  if (err) {
    switch (err.name) {
      case 'JsonWebTokenError':
        res.status(403).send({ code: -1, msg: '无效的token' });
        break;
      case 'TokenExpiredError':
        res.status(403).send({ code: -1, msg: 'token过期' });
        break;
    }
  }
})
}

// 5、当token到期时,客户端应该定期更新token,所以服务端还应该有更新token的操作

客户端拿到token后,需要保存到window.localStorage中

// 1、在客户端保存token
window.sessionStorage.setItem('uid', res.id)
// 2、使用编程式路由,进行重定向
this.$router.push('/')

客户端每次访问服务端的时候,在head中带上token。

axios.interceptors.request.use(config=>{
  config.headers.Authorization = window.sessionStorage.getItem('token')
  return config
})

客户端退出时

//清空token
window.sessionStorage.clear()
// 跳转到登录页
this.$router.push('/login')

十、新建项目配置

安装wrap console log,按Ctrl+Alt+W+W,可以快速写日志代码

Alt+Shift+A 出现注释

0 git常用命令

拷贝项目 git clone <仓库地址>
创建分支 git branch <name>
创建并进入分支 git checkout -b <name>
查看状态 git status
添加所有文件 git add
提交  git commit -m '注释'
拉取  git pull
推送  git push
查看分支  git branch --list
查看分支(包括远程分支)  git branch -a

参考 https://www.bilibili.com/video/BV1A4411Y7fi/?spm_id_from=333.788.videocard.1 全站之巅

参考 https://www.bilibili.com/video/BV1dg4y1q7K3?p=1 知识点最全

参考 https://www.bilibili.com/video/BV1S5411W794?p=31 量不大,封装组件

十一、 可视化面板

参考 https://www.bilibili.com/video/av582842505?p=1

十二、BootStrap

十三、前端页面布局

1、Flex布局

传统的布局方案,基于盒子模型,依赖display属性+position属性+float属性。但是对于特殊布局非常不方便,比如垂直居中就不容易实现。

2009年,W3C提出了一种新的方案——flex布局,可以简便、完整、响应式地实现各种页面布局。

1.1 Flex布局是什么

Flex是Flexible Box的缩写,意思是弹性布局,指的是相对于盒装模型提供了最大的灵活性。

任何一个容器都可以指定为Flex布局.

.box{
  display:flex;
}

行内元素,也可以使用flex布局

.box{
  display:inline-flex;
}

注意:使用flex布局后,子元素的float、clear、vertical-align属性将无效。

1.2 基本概念

采用flex布局的元素,成为Flex容器,简称容器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMgvBEjH-1608279639363)(3791e575c48b3698be6a94ae1dbff79d.png)]

容器存在两根轴:水平的主轴(main axis)和垂直的纵轴(cross axis)。

主轴的开始位置叫做main start,主轴的结束位置叫做main end。

纵轴的开始位置叫做cross start,纵轴的结束位置叫做cross end。

容器里面的叫做项目。项目默认沿着主轴排列。单个项目的高叫做main size,宽叫做cross size。

1.3 容器的属性

3.1 flex-direction属性

这表示项目的排列方向。

.box{
  flex-direction: row | row-reverse | column | column-reverse
}

有4个可选值:

  • row(默认值):主轴为水平方向,起点在左端

  • row-reverse:主轴为水平方向,起点在右端

  • column:主轴为垂直方向,起点在上沿

  • column-reverse:主轴为垂直方向,起点在下沿

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LV3qJccm-1608279639364)(0cbe5f8268121114e87d0546e53cda6e.png)]

3.2 flex-wrap属性

默认情况下,项目都排在一条线上。flex-wrap属性定义在一条轴上排不下,是否折行。

.box{
  flex-wrap: nowrap | wrap | wrap-reverse;
}

有3个可选值:

  • nowrap(默认值):不换行

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JK5Ojqd-1608279639366)(9da1f23965756568b4c6ea7124db7b9a.png)]

  • wrap:换行,第一行在上方

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clFDduJ0-1608279639367)(3c6b3c8b8fe5e26bca6fb57538cf72d9.jpg)]

  • wrap-reverse:换行,第一行在下方

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0cmrgWs-1608279639369)(fb4cf2bab8b6b744b64f6d7a99cd577c.jpg)]

3.3 flex-flow属性

是flex-direction属性和flex-wrap属性的简写形式,默认为row nowrap。

.box{
  flex-flow: <flex-direction>  <flex-wrap>;
}
3.4 justify-content属性

定义了项目在主轴上的对齐方式。

.box{
  justify-content: flex-start | flex-end | center | space-between | space-around;
}

可选值有:

  • flex-start(默认值):左对齐

  • flex-end:右对齐

  • center:居中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YILC5bu0-1608279639371)(1213309-20190806225320396-1174389728.png)]

  • space-between:两端对齐,项目之间的间隔都相等。左右两侧项目都紧贴容器。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCe6Dkza-1608279639372)(1213309-20190805182705299-5638313.png)]

  • space-around:每个项目两侧的间隔相等。项目之间的间隔比项目与边框的间隔大一倍。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KGd2fo4p-1608279639374)(1213309-20190805183114377-29193290.png)]

  • space-evenly:项目之间间距与项目和容器间距相等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5oNbaHj-1608279639375)(1213309-20190805183358497-472990119.png)]

3.5 align-items属性

定义项目在交叉轴上如何对齐。

.box{
  align-items: flex-start | flex-end | center | baseline | stretch;
}

可选值有:

  • flex-start:交叉轴的起点对齐;
  • flex-end:交叉轴的终点对齐;
  • center:交叉轴的中点对齐;
  • baseline:项目的第一行文字的基线对齐;
  • stretch(默认值):如果项目未设置高度或者设为auto,将占满整个容器的高度;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29UgeEVK-1608279639378)(2b0c39c7e7a80d5a784c8c2ca63cde17.png)]

3.6 align-content属性

定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

.box{
  align-content: flex-start | flex-end | center | space-between | space-around |stretch;
}

该属性可选值有6个:

  • flex-start:与交叉轴的起点对齐;

  • flex-end:与交叉轴的终点对齐;

  • center:与交叉轴的中点对齐;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mHdFrKm-1608279639380)(1213309-20190806230610292-2072864141.png)]

  • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LAUJsYmO-1608279639382)(1213309-20190806231128539-9221266.png)]

  • space-around:没跟轴线两侧的间隔都相等。轴线之间的间隔比轴线与边框的间隔大一倍。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-teW7OfRI-1608279639384)(1213309-20190806230909994-1088454199.png)]

  • stretch(默认值):轴线占满整个交叉轴

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0HGe60e-1608279639386)(1213309-20190806230418272-1997484074.png)]

1.4 项目的属性

4.1 order属性

定义项目的排列顺序。数值越小,排列越靠前,默认为0。

.item{
  order:<integer>;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tFHesDn6-1608279639388)(59e399c72daafcfcc20ede36bf32f266.png)]

4.2 flex-grow属性

定义项目的放大比例,默认为0,即如果存放剩余空间,也不放大。

假设默认三个项目中前两个个项目都是0,最后一个是1,最后的项目会沾满剩余所有空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQ1BjdEW-1608279639390)(1213309-20190808185733909-1052417826.png)]

假设只有第一个项目默认为0,后面两个项目flex-grow均为1,那么后两个项目平分剩余空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3ScMMHY-1608279639392)(1213309-20190808185832446-278289995.png)]

假设第一个项目默认为0,第二个项目为flex-grow:2,最后一个项目为1,则第二个项目在放大时所占空间是最后项目的两倍。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N74Jtksk-1608279639393)(1213309-20190808190032536-1526107911.png)]

4.3 flex-shrink属性

定义了项目的缩小比例,默认为1。即如果空间不足,该项目将缩小。

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性都是0,其他项目都是1,则空间不足时,前者不缩小。负值对该属性无效。

.item{
  flex-shrink:<number>;  /* 默认值1*/
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5ylyI1I-1608279639396)(1213309-20190808191100715-1387948858.gif)]

上图中第二个项目flex-shrink为0,所以自身不缩小。

4.4 flex-basis属性

定义了在分配多余空间之前,项目占用的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

.item{
  flex-basis: <length> | auto; /*默认auto*/
}
4.5 flex属性

是flex-grow、flex-shrink、flex-basis的简写,默认值是0 1 auto。后两个属性可选。

.item{
  flex:none | [<'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷键: auto(1 1 auto)和none(0 0 auto)。

4.6 align-self属性

允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

.item{
  align-self:auto | flex-start | flex-end | center | baseline | stretch;
}

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17Ef8Yql-1608279639398)(55b19171b8b6b9487d717bf2ecbba6de.png)]

2、网格布局

3、vh和vw自适应布局

十四、iview

1 安装

使用vue create xxx创建一个普通vue项目,然后添加vue add vuetify插件。

十三、前端页面布局

1、Flex布局

传统的布局方案,基于盒子模型,依赖display属性+position属性+float属性。但是对于特殊布局非常不方便,比如垂直居中就不容易实现。

2009年,W3C提出了一种新的方案——flex布局,可以简便、完整、响应式地实现各种页面布局。

1.1 Flex布局是什么

Flex是Flexible Box的缩写,意思是弹性布局,指的是相对于盒装模型提供了最大的灵活性。

任何一个容器都可以指定为Flex布局.

.box{
  display:flex;
}

行内元素,也可以使用flex布局

.box{
  display:inline-flex;
}

注意:使用flex布局后,子元素的float、clear、vertical-align属性将无效。

1.2 基本概念

采用flex布局的元素,成为Flex容器,简称容器

[外链图片转存中…(img-wMgvBEjH-1608279639363)]

容器存在两根轴:水平的主轴(main axis)和垂直的纵轴(cross axis)。

主轴的开始位置叫做main start,主轴的结束位置叫做main end。

纵轴的开始位置叫做cross start,纵轴的结束位置叫做cross end。

容器里面的叫做项目。项目默认沿着主轴排列。单个项目的高叫做main size,宽叫做cross size。

1.3 容器的属性

3.1 flex-direction属性

这表示项目的排列方向。

.box{
  flex-direction: row | row-reverse | column | column-reverse
}

有4个可选值:

  • row(默认值):主轴为水平方向,起点在左端

  • row-reverse:主轴为水平方向,起点在右端

  • column:主轴为垂直方向,起点在上沿

  • column-reverse:主轴为垂直方向,起点在下沿

    [外链图片转存中…(img-LV3qJccm-1608279639364)]

3.2 flex-wrap属性

默认情况下,项目都排在一条线上。flex-wrap属性定义在一条轴上排不下,是否折行。

.box{
  flex-wrap: nowrap | wrap | wrap-reverse;
}

有3个可选值:

  • nowrap(默认值):不换行

    [外链图片转存中…(img-8JK5Ojqd-1608279639366)]

  • wrap:换行,第一行在上方

    [外链图片转存中…(img-clFDduJ0-1608279639367)]

  • wrap-reverse:换行,第一行在下方

    [外链图片转存中…(img-C0cmrgWs-1608279639369)]

3.3 flex-flow属性

是flex-direction属性和flex-wrap属性的简写形式,默认为row nowrap。

.box{
  flex-flow: <flex-direction>  <flex-wrap>;
}
3.4 justify-content属性

定义了项目在主轴上的对齐方式。

.box{
  justify-content: flex-start | flex-end | center | space-between | space-around;
}

可选值有:

  • flex-start(默认值):左对齐

  • flex-end:右对齐

  • center:居中

    [外链图片转存中…(img-YILC5bu0-1608279639371)]

  • space-between:两端对齐,项目之间的间隔都相等。左右两侧项目都紧贴容器。

    [外链图片转存中…(img-wCe6Dkza-1608279639372)]

  • space-around:每个项目两侧的间隔相等。项目之间的间隔比项目与边框的间隔大一倍。

    [外链图片转存中…(img-KGd2fo4p-1608279639374)]

  • space-evenly:项目之间间距与项目和容器间距相等

[外链图片转存中…(img-h5oNbaHj-1608279639375)]

3.5 align-items属性

定义项目在交叉轴上如何对齐。

.box{
  align-items: flex-start | flex-end | center | baseline | stretch;
}

可选值有:

  • flex-start:交叉轴的起点对齐;
  • flex-end:交叉轴的终点对齐;
  • center:交叉轴的中点对齐;
  • baseline:项目的第一行文字的基线对齐;
  • stretch(默认值):如果项目未设置高度或者设为auto,将占满整个容器的高度;

[外链图片转存中…(img-29UgeEVK-1608279639378)]

3.6 align-content属性

定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

.box{
  align-content: flex-start | flex-end | center | space-between | space-around |stretch;
}

该属性可选值有6个:

  • flex-start:与交叉轴的起点对齐;

  • flex-end:与交叉轴的终点对齐;

  • center:与交叉轴的中点对齐;

    [外链图片转存中…(img-4mHdFrKm-1608279639380)]

  • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布;

    [外链图片转存中…(img-LAUJsYmO-1608279639382)]

  • space-around:没跟轴线两侧的间隔都相等。轴线之间的间隔比轴线与边框的间隔大一倍。

    [外链图片转存中…(img-teW7OfRI-1608279639384)]

  • stretch(默认值):轴线占满整个交叉轴

[外链图片转存中…(img-Y0HGe60e-1608279639386)]

1.4 项目的属性

4.1 order属性

定义项目的排列顺序。数值越小,排列越靠前,默认为0。

.item{
  order:<integer>;
}

[外链图片转存中…(img-tFHesDn6-1608279639388)]

4.2 flex-grow属性

定义项目的放大比例,默认为0,即如果存放剩余空间,也不放大。

假设默认三个项目中前两个个项目都是0,最后一个是1,最后的项目会沾满剩余所有空间。

[外链图片转存中…(img-gQ1BjdEW-1608279639390)]

假设只有第一个项目默认为0,后面两个项目flex-grow均为1,那么后两个项目平分剩余空间。

[外链图片转存中…(img-x3ScMMHY-1608279639392)]

假设第一个项目默认为0,第二个项目为flex-grow:2,最后一个项目为1,则第二个项目在放大时所占空间是最后项目的两倍。

[外链图片转存中…(img-N74Jtksk-1608279639393)]

4.3 flex-shrink属性

定义了项目的缩小比例,默认为1。即如果空间不足,该项目将缩小。

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性都是0,其他项目都是1,则空间不足时,前者不缩小。负值对该属性无效。

.item{
  flex-shrink:<number>;  /* 默认值1*/
}

[外链图片转存中…(img-U5ylyI1I-1608279639396)]

上图中第二个项目flex-shrink为0,所以自身不缩小。

4.4 flex-basis属性

定义了在分配多余空间之前,项目占用的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

.item{
  flex-basis: <length> | auto; /*默认auto*/
}
4.5 flex属性

是flex-grow、flex-shrink、flex-basis的简写,默认值是0 1 auto。后两个属性可选。

.item{
  flex:none | [<'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷键: auto(1 1 auto)和none(0 0 auto)。

4.6 align-self属性

允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

.item{
  align-self:auto | flex-start | flex-end | center | baseline | stretch;
}

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

[外链图片转存中…(img-17Ef8Yql-1608279639398)]

2、网格布局

3、vh和vw自适应布局

十四、iview

1 安装

使用vue create xxx创建一个普通vue项目,然后添加vue add vuetify插件。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值