前端笔记

一、 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 = `
` 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 = `
姓名:
年龄:
` 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 = `
姓名:
年龄:
` 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 = `
姓名年龄
aaaaaaaa
aaaaaaaa
添加 ` form_html = `
姓名:
年龄:
` function readJSON(){ let data = fs.readFileSync(__dirname+'/data.json').toString() data = JSON.parse(data) console.log('读取到的JSON数据', data) return data } function fillTable(jsondata){ head = `` jsondata.forEach((stu, index)=>{ //let row = '' let row = `` //console.log('第'+index+'行', row) head += row }) tail = `
姓名年龄
'+stu.name+''+stu.age+'
${stu.name}${stu.age}
添加` 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}}
  {
    {$value.name}}{
    {$value.age}}
{
    {/each}}

7.3 模板继承

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

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

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

下面是骨架模板





    
    {
    {block 'title'}}My Site{
    {/block}}

    {
    {block 'head'}}
    
    {
    {/block}}


    {
    {block 'content'}}{
    {/block}}


下面是继承后的模板


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

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

{
    {block 'head'}}
    
{
    {/block}}

{
    {block 'content'}}

This is just an awesome page.

{ {/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

首先看一下页面的结构

接下来看一下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)
});

如果要在页面上输出




// 假设页面有下面这个span
    
用户名:
密码:
验证码:

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内容

    
        {
    {each stuList}}
        
        {
    {/each}}
    
{ {$value.id}} { {$value.name}} { {$value.age}}

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文件

    
用户名:
密码:

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、编辑操作

在页面使用

修改

在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"

    
用户名:
密码:
年龄:
照片:

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)
});

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




    
    



   
    
用户名:
密码:
验证码:

登录时,进行验证

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渲染




    
    
{ {msg}}

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




    
    
{ {msg}}
如果要获取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() }}	//方法


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

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




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

我是超链接

2.3 输出一次 v-once

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

{ {msg}}

2.4 显示或隐藏 v-show

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

显示

当是false时,会替换为

显示

2.5 属性值单向绑定v-bind

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

超链接

// 点击这里  可以简写   点击这里

动态参数 ...

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

2.6 输入值双向绑定v-model

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


2.7 事件绑定v-on

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



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

也可以传递参数


methods: {click1:(v)=>console.log(v)},

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

还可以传递事件


可以在方法中,使用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)
        },
    })

动态参数 ...

2.8 if判断

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

A
B
C
Not A/B/C

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

2.9 for循环

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

  • 索引:{ {i}} 值:{ {item}}
  • 下面是遍历一个对象中的属性和值

  • 索引:{ { index }} 属性:{ { key }} 值:{ { value }}
  • 下面是遍历一个整数

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

    2.10 计算属性

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

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

    { { msg.split('').reverse().join('') }}

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

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

    Original message: "{ { msg }}"

    Computed reversed message: "{ { reversedMessage }}"

    计算属性和方法的区别:

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

    2.11 监听器watch

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

    
    

    这种情况下,也可以使用计算属性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 事件修饰符

    
    
    
    
    
    ...
    ...

    2.13 样式绑定

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

    也可以绑定class对象

    data: { classObject: { active: true } }

    也可以传递数组

    data: { activeClass: 'active', errorClass: 'text-danger' } // 渲染为

    2.14 表单处理

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

    普通文本框

    
    

    多行文本框

    
    

    多选框

    
        {
        {item}}
    
    

    单选框

    
         {
        {item}}
    
    

    下拉框

    
    

    数据同步的修饰符

    
    
    
    
    
    
    
    
    

    2.15 自定义指令

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

    页面载入时,input 元素自动获取焦点:

    自定义指令使用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文件,可以作为一个模块。那么,一个模块通常会向外暴露很多可供外部使用的变量和方法。常使用下面的形式

    
    

    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)==定义。

    下面是定义组件

    
    

    下面是使用组件

    3.1 全局组件和局部组件

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

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

    3.2 传值:父传子

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

    Vue.component('grid-position', {
    	props: ['option'],
    	template: `
    		
    		  
    		      
    		  
    		  
    		      
    		  
    		  
    		      
    		  
    		  
    		      
    		  
    		  位置
    		
    		`
    })
    

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

    
    

    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: () => {}
        }
      }
    

    传递一个对象的所有属性

    
    等价于下面的写法
    
    
    

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

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

    子组件必须指定属性ref

    
    

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

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

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

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

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

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

    
    
    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
    
    子组件要想改变父组件的值,需要使用$emit发射update事件
    this.$emit('update:foo', newValue)
    

    3.6 双向传值:v-model

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

    
    // 相当于
    
    

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

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

    下面是父组件调用

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

    Vue.component('input-price', {
        // 2、当有数据输入时触发了该组件的input事件
        template: '',
        // 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分发负责。

    无名插槽

    有名插槽

    12345 56789

    插槽作用域

    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指令的值即可。

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

    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: '
    User { { $route.params.id }}
    ' }

    需要注意的是,动态路由会让组件重用,不是新建组件。所以,组件的生命周期函数不会调用,为了再次调用生命周期函数中的方法,需要监控路由的变化,使用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.sync Array true 布局位置。数组中每个元素是GridItem。每个元素必须有i、x、y、w、h属性。
    colNum Number false 12 表格有多少列
    rowHeight Number false 150 一行的高度
    isDraggable Boolean false true 是否允许拖拽
    isResizable Boolean false true 是否允许缩放
    isMirrored Boolean false false 是否允许替换翻转
    verticalCompact Boolean false true 是否允许垂直压缩
    margin Array false [10,10] 边距,必须有2个元素,第1个数字表示水平边距,第2个数字表示垂直边距。单位是像素。
    useCssTransforms Boolean false true 标识是否使用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属性

    属性名称 类型 必填 注释
    i String true 元素的唯一ID
    x Number true 位于第几列
    y Number true 位于第几行
    w Number true 初始宽度,是colWith的倍数
    h Number true 初始高度,是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 
    创建并进入分支 git checkout -b 
    查看状态 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:   ;
    }
    

    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:;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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:;  /* 默认值1*/
    }
    

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

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

    4.4 flex-basis属性

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

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

    .item{
      flex-basis:  | 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:   ;
    }
    

    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:;
    }
    

    [外链图片转存中…(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:;  /* 默认值1*/
    }
    

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

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

    4.4 flex-basis属性

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

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

    .item{
      flex-basis:  | 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插件。

    你可能感兴趣的:(前端,node.js,javascript,vue.js)