参考见 https://es6.ruanyifeng.com/
在1996年以前,浏览器是不支持脚本语言的,只支持html和css。
NetScape网景公司提出了前端编程语言,为了蹭java的热度,起名为JavaScript。并且经过与Sun公司的授权,网景公司将JavaScript注册为商标。
1996年,网景公司将该脚本提交给标准委员会ECMA。1997年,ECMA将其命名为ECMAScript。因此ECMAScript是国际标准,JavaScript是标准的实现。
2011年,ECMAScript5.1发布。自此以后,新的标准更新特别频繁,以后统称为ES6。
目前的主流浏览器都支持ES5,但是对ES6的支持不全面。
Bable是一个ES6转码器,可以将ES6代码转为ES5代码,从而在老的浏览器执行。
Bable的配置文件时.babelrc,存放在项目的根目录下。配置文件的基本规则是
{
"presets": [],
"plugins": []
}
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定义的常量不能被重写赋值
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 //块级作用域的范围仅限于{...}之内
类似于多重赋值,使用符号[]或者{}。
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')
比如“\u0061”表示”a“。
// 遍历字符串
for(let i of 'foo'){
console.log(i)
}
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)
标签模板相当于函数调用。飘号相当于函数的参数。
alert`hello` 等同于 alert(['hello'])
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'
es6之前的函数参数,不能使用默认值。
function log(x, y='world'){...}
// 定义
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') //报错
es6引入rest参数(形式为 …变量名),用于获取函数的多余参数。rest参数搭配的变量是一个数组。
function add(...values)
add(1,2,3)
// 有名函数
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
ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
const name = "吴超"
const baz = {name} // {name:"吴超"}
上面的代码中,变量foo直接写在大括号中。这时,属性名就是变量名,属性值就是变量值。
function f1(name, age){
return {name, age}
}
f1("张三", 23) // Object {name:'张三', age:23}
以前的写法是
{
f1:function(){return "f1"}
}
新的写法是
{
f1(){return "f1"}
}
现在,使用属性、方法的简写,就可以非常方便的定义一个对象
var id = 1
const person = {
id,
name:'张三',
say(msg){
return 'hello '+msg
}
}
如果对象嵌套对象,读取最内层对象的属性msg.body.user.name
,如果一个对象不存在,就报错。
使用链判断运算符?.
,不存在,则返回undefined,最好指定默认值。
// 对象判断链
msg?.body?.user?.name|| 'default'
// 数组判断链
obj?.[1]
// 函数判断链
a?.b()
nodejs是一个基于chrome v8引擎的js运行环境。
nodejs是让js运行在服务端。nodejs是服务端的开发框架。 bs/cs
事件驱动、异步处理、非阻塞、高并发、单进程单线程
“拉”模式
“推”模式
v8、libuv(event loop)、js库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDfSr8Qz-1608279639345)(vuejs学习笔记.assets/u=2398826131,3965435416&fm=26&gp=0.jpg)]
略
不建议装到有中文有空格的路径下。
vs code
.exit 退出命令行的方法
global对象,等价于浏览器中的windows对象,是全局的。
console.log('文件名', __filename)// 文件名 F:\学佳澳\高校实训20-24日\代码\day0106_global全局变量.js
console.log('目录位置', __dirname) //目录位置 F:\学佳澳\高校实训20-24日\代码
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) // 停止前面的周期性调用
是用的最频繁的一个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')// 记录一个结束时间
获取操作系统的进程信息,与操作系统交互
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() 终止进程
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('--------------------------------------', )
设计上应该是借鉴了操作系统的思路。
事件机制中,由三部分组成:事件、事件发出、事件接收处理
nodejs负责接收事件、处理事件
我们可以发出事件、自定义处理事件的方法、注册事件处理方法
// 引入events事件模块
const events = require('events')
// 创建类EventEmitter的对象
var eventEmitter = new events.EventEmitter()
// 事件机制中,由三部分组成:事件、事件发出、事件接收处理
// nodejs负责接收事件、处理事件
// 我们可以发出事件、自定义处理事件的方法、注册事件处理方法
eventEmitter.on('event1', ()=>{
console.log('处理事件', )
});
// 触发事件
eventEmitter.emit('event1');
事件机制,可以让行为和操作相分离,也属于解耦的一种方式。
编程语言自身提供一些开发包/api,更复杂的更多的功能需要模块提供。
一般一个js文件,就可以作为一个模块。模块间可以互相调用。
关键词:exports、module.exports、require
简单理解: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(node package manager)是nodejs的 包管理工具,用于node插件的管理(包括安装、卸载、管理依赖等)。
npm是随同nodejs一起安装的管理工具。
java领域有类似的是maven,linux领域有类似的yum、rpm
因为nodejs做的很小巧、高内聚。为了丰富功能,通过package的形式扩展。
使用npm下载模块的时候,自动下载依赖的模块和对应的版本。
通过初始化项目,可以创建一个package.json文件。
npm init 项目名称
在package.json里面记录了项目依赖的模块。当把项目给别人时,别人执行npm install即可把依赖项安装,非常方便。
需要知道模块名称即可。
npm install xxx
在国外有一个nodejs的模块仓库,免费的,谁都可以下载。当我们执行install的时候,就去这个仓库寻找特定的模块和指定的版本。下载到本地。
下载到本地项目的node_modules文件夹中,同时注册到package-lock.json文件中。
使用参数-g可以安装到全局。
npm root -g //可以查看全局模块库的位置
npm install xxx -g
npm uninstall xxx //卸载模块
npm5之前,不会生成pacakge-lock.json文件。
package.json文件锁定的是大版本,不关心小版本。
package-lock.json文件锁定小版本。
使用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 <命令项>的形式执行。
使用淘宝镜像
npm i cnpm -g --registry=https://registry.npm.taobao.org
npm i nrm -g // 安装nrm
nrm ls //查看有哪些源仓库
nrm use taobao //指定使用哪个源仓库
api中由同步和异步两个版本,建议使用异步版本。
const fs = require('fs')
比如可以判断是文件还是文件夹?还可以知道文件大小、创建者、创建时间、权限等等。
// 访问文件信息,在进行文件夹遍历的时候,需要查看类型和基本信息
fs.stat('module01.js', (err, data)=>{
console.log('类型', data.isFile()?'文件':'文件夹')
console.log('文件信息', data)
})
读文件的时候,回调函数中,首先要进行错误判断,如果有错误,输出错误原因后返回;
fs.readFile('module01.jsasfd', (err, data)=>{
if(err){
console.error('出错了', '文件不存在')
return
}
console.log('文件内容', data.toString())
})
// 每次新创建一个文件。如果已经存在,则先删除再创建
fs.writeFile('a.txt', '我的名字叫吴超,年龄23',()=>{})
// 追加
// fs.writeFile('a.txt', '我的名字叫吴超,年龄23',{flag:'a'}, ()=>{})
在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'))
Stream类是个抽象接口,有四种类型:Readable、Writable、Duplex、Transform类型。
所有的Stream对象都是EventEmitter的实例。
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)
})
const fs = require('fs')
const writeStream = fs.createWriteStream('b.txt')
writeStream.on('finish', ()=>console.log('写完了'))
writeStream.write('hello world我的祖国')
// 调用end方法时,触发finish事件
writeStream.end('完成写入数据');
使用第三方库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)
知识点:路径的遍历、判断文件类型、判断文件是否存在、创建文件夹、数据复制
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)
网络请求,知道请求路径是什么,请求参数是什么,可以响应结果。
http://localhost:3000/hello/?name=wuchao&age=23
const http = require('http')
const url = require('url')
const util = require('util')
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('服务器启动了'))
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)
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('服务器启动了', ))
为了解决重复提交,使用重定向。
在应答中使用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('服务器启动了', ))
// 把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)
})
用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 = `
姓名 年龄
aaaa aaaa
aaaa aaaa
添加
`
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 = ''+stu.name+' '+stu.age+' '
let row = `${stu.name} ${stu.age} `
//console.log('第'+index+'行', row)
head += row
})
tail = `
添加`
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是基于nodejs的web框架。
可以在修改代码的时候,重启应用nodemon xxx.js
//加载模块
const express = require('express')
// 创建web服务器
const app = express()
// 启动服务器
app.listen(3000, console.log('服务器启动了,监听3000端口' ))
// 响应get请求
app.get('/', (req, res)=>{
// 输出文本
// res.send('hello express')
// 输出json
// res.send({'name':'wuchao', 'age':23})
// 输出html文件,使用绝对路径
// res.sendFile(__dirname+'/index.html')
// 渲染模板
// res.render(...)
})
对于普通的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)
})
引入第三方模块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)
})
app.use(express.json())
当请求路径特别多的时候,我们需要对每一个请求单独做出响应,这时候就会产生大量的代码,堆积在一起。使用路由,可以让请求的路径结构更加清晰,同时也可以分模块处理。
思路:对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....', ))
常用的操作:输出、判断、循环
art-template模板既可以用在前端js,也可以用在后端js。
安装模板
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});
if是用于判断的。
{
{if msg}}
{
{msg}}
{
{/if}}
each是循环数组stu,stu里面的每一个元素是一个json,在循环体内使用$value表示每一个循环遍历,使用 ¥ index表示循环序号。
{
{each stu}}
{
{$value.name}} {
{$value.age}}
{
{/each}}
模板继承允许构建 一个模板的骨架,然后向里面填充组成部分。
基本语法如下,在骨架模板中适宜{ {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}}
把某一个公共部分,加入到当前页面中来。
{
{include './header.art'}}
{
{include './header.art' data}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UR6Mfeq5-1608279639349)(vuejs学习笔记.assets/1281517-20180205155942998-197158086.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5SpHacp-1608279639352)(…/…/…/_学习笔记/前端笔记/nodejs/1281517-20180205160104326-1983829336.png)]
使用第三方模块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('销毁')})
会话的配置项有
使用中间件完成权限控制。
app.use((req, res, next)=>{
console.log(req.url, )
if(没有权限){
res.redirect('/') //重定向
return;
}
next() // 放行
})
数据的范围可以是res、session或者app。指定数据范围的目的是为了向页面传值,以及在不同的express方法之间传值。
应用级别使用app.locals,会保存在应用的整个生命周期;
响应级别使用res.locals,会保存在本次请求应答的生命周期中;
app.locals
res.locals.session = req.session
使用第三方模块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的配置有:
下载文件只需要使用==res.download(文件路径,下载文件名,回调函数)==即可
app.get('/download',function (req,res) {
res.download(__dirname+'/route.js','route.js',
function (err) {
if(err){
console.log(err)
}
})
})
使用第三方模块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
所谓的静态文件,指的是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 就可以访问到该文件。
安装模块cors,增加下面一行即可。
app.use(require('cors')())
使用第三方模块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();
});
});
分模块操作,可以让逻辑更加清晰。
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)
创建一个文件夹stu-mis
进入这个文件夹,在命令行执行npx express-generator
,生成模板项目
可以删除views文件夹中的所有模板文件。在命令行执行npm install art-template express-art-template
,安装art模板引擎。
在命令行执行npm install
,安装模块
修改app.js的内容的第14行
app.set('html', require('express-art-template'));
修改app.js的内容的第20行
app.use('/static', express.static(path.join(__dirname, 'public')));
这样,就可以使用/static
访问静态内容了。
修改index.js文件,
res.render('index.html');
在views文件夹中创建index.html文件
在命令行执行nodemon
,运行项目。在浏览器访问http://localhost:3000 就能看到首页面。
在vscode中安装 EJS language support 插件
至此,架子搭建完毕。
修改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}}
{
{$value.id}}
{
{$value.name}}
{
{$value.age}}
{
{/each}}
创建数据库访问模块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);
});
这样,以后就可以非常方便的使用数据库了。
在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文件
修改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}}
router.get('/logout', function(req, res){
req.session.destroy((err)=>{});
res.redirect('/');
});
在页面使用
修改
在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('/')
});
});
参考编辑操作,略
上传文件的表单,使用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('/')
});
});
只需要使用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);
});
});
使用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')
}
});
});
使用npm install mongoose -S
,按照mongoose
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中引入,与模型无关。这样,一次性加载数据库连接。
下面的模型中,使用了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), //对密码加密
},
})
);
// 保存对象,如果没有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
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}
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} .....`));
@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
按照模块 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
Vue是一套用于构建用户界面的渐进式js框架,发布于2014年。
Vue被设计为可以自底向上逐层应用。
Vue的核心库只关注视图层。
最大优点是易于上手、还有丰富的生态圈。
原生js——>jquery类库——>Vue.js/Angular.js/React.js
虚拟dom、双向绑定、整套解决方案、前后端分离
三部曲【1】引入js库【2】指定dom id【3】vuejs渲染
{
{msg}}
还有更惊艳的,可以实现输入框与显示的同步。
{
{msg}}
如果要获取vm中的内容,可以使用vm.$el 或者 vm.$data
指的是上下层分离
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIAi0TGd-1608279639357)(image-20200723054735539.png)]
view【dom元素】——ViewModel【这是vuejs的核心部分】——Model【js对象】
VM会监听到视图的变化,并通知数据发生变化;VM能观察到数据的变化,更新视图内容。这就是双向绑定。
如何随时知道dom数据的变化哪?【1】vue通过Object.defineProperty()劫持虚拟dom对应数值的set和get方法【2】angular通过检测input、change等事件。这个负责劫持或者检测的,称为观察者。
如何在数据变化的时候更新dom视图哪?解析指令语言,获取其中的数据模型。当数据模型变化时,通知dom更新。称为编译器。
安装时,使用如下命令。目前的版本是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
}
}
表示指明端口,并且自动打开浏览器。
插值表达式是用于把vue对象中的属性和方法输出。
{
{ msg }} // 基本类型
{
{ [0,1,2,3,4][0] }} //数组
{
{ {'name':'张三'}.name }} //对象
{
{ sayHi() }} //方法
使用v-html后,内部会被html渲染。
上面代码渲染后,就是下面的样子。只是v-html会渲染样式,v-text不会渲染。
我是超链接
只取一次值,后续msg的改变,不会更新视图。
{
{msg}}
显示还是隐藏,接收逻辑值。
显示
当是false时,会替换为
用在属性前面,可以绑定属性值。单向绑定。
超链接
// 点击这里 可以简写 点击这里
动态参数 ...
在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写。
绑定input元素。当input值改变时,vue数据也会改变;当vue数据改变时,input值也会改变。双向绑定。
绑定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)
},
})
动态参数 ...
三个原语:v-if、v-else-if、v-else
A
B
C
Not A/B/C
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,这么做除了使 Vue 变得非常快。但是带来一个问题:如果复用了相同的组件,那么组件的内容不会被更新,因为vue认为是同一个组件,不需要再次渲染。怎么办?只需添加一个具有唯一值的 key
attribute 即可。
下面是遍历一个数组,注意索引i是在后面
索引:{
{i}} 值:{
{item}}
下面是遍历一个对象中的属性和值
索引:{
{ index }} 属性:{
{ key }} 值:{
{ value }}
下面是遍历一个整数
{
{ n }}
使用for循环渲染组件的时候,必须使用key。
定义的是一个方法,但是用法上是一个属性。使用计算属性的目的是为了解决表达式中不可能完成复杂的逻辑。
下面的代码中有很多操作,显得很臃肿
{
{ 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没有变化,直接返回结果。
数据之间有依赖关系,比如下面的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更合适。比如省市县联动功能的实现。
...
...
当isActive的是真值时,class的值是“static active”;否则是“static”。
也可以绑定class对象
data: {
classObject: {
active: true
}
}
也可以传递数组
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
// 渲染为
表单输入值使用v-model,可以实现双向绑定。
普通文本框
多行文本框
多选框
{
{item}}
单选框
{
{item}}
下拉框
下面是一个自定义指令的例子
页面载入时,input 元素自动获取焦点:
自定义指令使用Vue.directive(‘指令名’, 指令定义)。
在指令定义中,主要是实现钩子函数。
钩子函数的参数有:
el: 指令所绑定的元素,可以用来直接操作 DOM 。
binding: 一个对象,包含以下属性:
vnode: Vue 编译生成的虚拟节点。
oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
很多情况下,只关注bind和update钩子函数执行情况,可以缩写如下
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
自定义的事件名不需要大小写转换。
this.$emit('myEvent')
一个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"));
}
})
组件就是单独的一个Vue实例。使用==Vue.component(tagName, options)==定义。
下面是定义组件
下面是使用组件
刚才定义的就是全局组件,局部组件是限定在某个vue实例中使用。
组件内部只能有一个根元素。
子组件使用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写法。
子组件必须指定属性ref
父组件就可以使用this.$ref.childtpl 获得该子组件,就可以访问该子组件中的数据、方法了。
需要使用自定义事件完成子组件向父组件传递数据。
在子组件中,调用$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访问其中的属性和方法了。
当子组件修改父组件中的属性值时,就可以使用sync修饰符。
父组件调用子组件传值时,使用sync
子组件要想改变父组件的值,需要使用$emit发射update事件
this.$emit('update:foo', newValue)
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;
}
}
});
$parent
和$children
原理是使用公共的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))
假如父组件需要在子组件内放一些DOM,那么这些DOM是显示或者隐藏,在哪个地方显示,怎么显示,需要slot分发负责。
12345
56789
动态组件指的是把几个组件,放在一个挂载点下,根据父组件的变量决定显示哪个。
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属性。
按需加载组件
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
使用render函数我们可以用js语言来构建Dom
axios时一个基于Promise用于浏览器和nodejs的http客户端。
从浏览器中创建XMLHttpRequest
支持Promise API
客户端支持防止CSRF
提供了一些并发请求的接口
从nodejs创建http请求
拦截请求和响应
转换请求和响应数据
取消请求
自动转换json数据
引入axios模块
# 1、安装
npm install axios --save
# 2、在某个vue文件中配置引入
import axios from 'axios'
// 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);
});
除此之外,还有更多方法,如
建立文件夹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);
}
// 自定义实例,这样添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
// 同时发送一组请求
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方法中每个请求返回的响应。
参考 https://www.jianshu.com/p/4c5c99abb864
vue-router是在spa中建立url与页面的对应关系。
路由有两种模式,一种是在url上带上链接信息hash模式(即#hash),一种是通过浏览器的状态控制history模式。
方式1:直接修改地址栏
方式2:编程式导航:this.$router.push(‘路由地址’)
方式3:声明式导航:
下载npm install vue-router
在main.js中引入 import VueRouter from ‘vue-router’
安装插件Vue.use(VueRouter)
创建路有对象并配置路由规则
let router = new VueRouter({routes:[
{path:'/home', component:()=>import('@/home.vue')}
]})
将其路由对象传递给Vue实例
在app.vue中留坑
指的是路由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()
}
}
可以监控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`
}
}
用于在一个页面内显示一个新内容。
因此,需要在原页面的路由中增加children子元素
{
path: '/user',
component: User,
children: [
{
path: 'add',
component: AddUser
}
]
},
在user页面增加,用于显示嵌套的页面。
可以使用动态路由的方式,指向子页面
this.$router.push({ path: '/user/add' })
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
使用命名路由,可以在一个地方配置路由路径。
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
重定向也是通过 routes
配置来完成,下面例子是从 /a
重定向到 /b
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
// 下面是重定向到一个有名的路由
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props
将组件和路由解耦:
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");
});
// 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 ....})
State提供唯一的公共数据源。
// 定义全局变量
export default new Vuex.Store({
state:{ count:0 }
})
// 访问state中数据的第1种方式
this.$store.state.全局数据名称
// 访问state中数据的第2种方式
import {mapState} from 'vuex'
computed:{
...mapState(['count']) // 将全局数据,映射为当前组件的计算属性
}
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']) 把全局函数映射为组件内部的函数
}
专门用于处理异步的任务。需要在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'})
}
用于对store中的数据进行加工,产生新的数据,不会改变原始数据。
state:{
count:0
},
getters:{
showNum:state=>{
return '新数字'+state.count
}
}
使用getters
// 第1种方式
this.$store.getters.名称
// 第2种方式
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['showNum'])
}
为了避免出现同名变量或者函数冲突的情况,可以使用命名空间,即模块。
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)
引用。
webpack是一个前端资源的加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
# 1、安装
npm install vue-grid-layout --save
# 2、使用组件,两个核心组件
import { GridLayout,GridItem } from 'vue-grid-layout';
export default {
components: {
GridLayout, GridItem
}
}
有两个核心类GridLayout
和GridItem
。
属性名称 | 类型 | 必填 | 默认值 | 注释 |
---|---|---|---|---|
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定位 |
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)
}
属性名称 | 类型 | 必填 | 注释 |
---|---|---|---|
i | String | true | 元素的唯一ID |
x | Number | true | 位于第几列 |
y | Number | true | 位于第几行 |
w | Number | true | 初始宽度,是colWith的倍数 |
h | Number | true | 初始高度,是rowHeight的倍数 |
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);
},
使用token认证的方式:
客户端不需要持有密钥,由服务端通过密钥生成Token。
客户端登录时通过账号和密码到服务端进行认证,认证通过后,服务端通过持有的密钥生成Token,Token中一般包含失效时长和用户唯一标识,如用户ID,服务端返回Token给客户端。
客户端保存服务端返回的Token。
客户端进行业务请求时在Head的Authorization字段里面放置Token,如:Authorization: Bearer Token
服务端对请求的Token进行校验,并通过Redis查找Token是否存在,主要是为了解决用户注销,但Token还在时效内的问题,如果Token在Redis中存在,则说明用户已注销;如果Token不存在,则校验通过。
服务端可以通过从Token取得的用户唯一标识进行相关权限的校验,并把此用户标识赋予到请求参数中,业务可通过此用户标识进行业务处理。
用户注销时,服务端需要把还在时效内的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 出现注释
拷贝项目 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
传统的布局方案,基于盒子模型,依赖display属性+position属性+float属性。但是对于特殊布局非常不方便,比如垂直居中
就不容易实现。
2009年,W3C提出了一种新的方案——flex布局,可以简便、完整、响应式地实现各种页面布局。
Flex是Flexible Box的缩写,意思是弹性布局,指的是相对于盒装模型提供了最大的灵活性。
任何一个容器都可以指定为Flex布局.
.box{
display:flex;
}
行内元素,也可以使用flex布局
.box{
display:inline-flex;
}
注意:使用flex布局后,子元素的float、clear、vertical-align属性将无效。
采用flex布局的元素,成为Flex容器,简称容器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMgvBEjH-1608279639363)(3791e575c48b3698be6a94ae1dbff79d.png)]
容器存在两根轴:水平的主轴(main axis)和垂直的纵轴(cross axis)。
主轴的开始位置叫做main start,主轴的结束位置叫做main end。
纵轴的开始位置叫做cross start,纵轴的结束位置叫做cross end。
容器里面的叫做项目
。项目默认沿着主轴排列。单个项目的高叫做main size,宽叫做cross size。
这表示项目的排列方向。
.box{
flex-direction: row | row-reverse | column | column-reverse
}
有4个可选值:
row(默认值):主轴为水平方向,起点在左端
row-reverse:主轴为水平方向,起点在右端
column:主轴为垂直方向,起点在上沿
column-reverse:主轴为垂直方向,起点在下沿
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LV3qJccm-1608279639364)(0cbe5f8268121114e87d0546e53cda6e.png)]
默认情况下,项目都排在一条线上。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)]
是flex-direction属性和flex-wrap属性的简写形式,默认为row nowrap。
.box{
flex-flow: ;
}
定义了项目在主轴上的对齐方式。
.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)]
定义项目在交叉轴上如何对齐。
.box{
align-items: flex-start | flex-end | center | baseline | stretch;
}
可选值有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29UgeEVK-1608279639378)(2b0c39c7e7a80d5a784c8c2ca63cde17.png)]
定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.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)]
定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item{
order:;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tFHesDn6-1608279639388)(59e399c72daafcfcc20ede36bf32f266.png)]
定义项目的放大比例,默认为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)]
定义了项目的缩小比例,默认为1。即如果空间不足,该项目将缩小。
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性都是0,其他项目都是1,则空间不足时,前者不缩小。负值对该属性无效。
.item{
flex-shrink:; /* 默认值1*/
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5ylyI1I-1608279639396)(1213309-20190808191100715-1387948858.gif)]
上图中第二个项目flex-shrink为0,所以自身不缩小。
定义了在分配多余空间之前,项目占用的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
.item{
flex-basis: | auto; /*默认auto*/
}
是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)。
允许单个项目有与其他项目不一样的对齐方式,可覆盖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)]
使用vue create xxx
创建一个普通vue项目,然后添加vue add vuetify
插件。
传统的布局方案,基于盒子模型,依赖display属性+position属性+float属性。但是对于特殊布局非常不方便,比如垂直居中
就不容易实现。
2009年,W3C提出了一种新的方案——flex布局,可以简便、完整、响应式地实现各种页面布局。
Flex是Flexible Box的缩写,意思是弹性布局,指的是相对于盒装模型提供了最大的灵活性。
任何一个容器都可以指定为Flex布局.
.box{
display:flex;
}
行内元素,也可以使用flex布局
.box{
display:inline-flex;
}
注意:使用flex布局后,子元素的float、clear、vertical-align属性将无效。
采用flex布局的元素,成为Flex容器,简称容器
[外链图片转存中…(img-wMgvBEjH-1608279639363)]
容器存在两根轴:水平的主轴(main axis)和垂直的纵轴(cross axis)。
主轴的开始位置叫做main start,主轴的结束位置叫做main end。
纵轴的开始位置叫做cross start,纵轴的结束位置叫做cross end。
容器里面的叫做项目
。项目默认沿着主轴排列。单个项目的高叫做main size,宽叫做cross size。
这表示项目的排列方向。
.box{
flex-direction: row | row-reverse | column | column-reverse
}
有4个可选值:
row(默认值):主轴为水平方向,起点在左端
row-reverse:主轴为水平方向,起点在右端
column:主轴为垂直方向,起点在上沿
column-reverse:主轴为垂直方向,起点在下沿
[外链图片转存中…(img-LV3qJccm-1608279639364)]
默认情况下,项目都排在一条线上。flex-wrap属性定义在一条轴上排不下,是否折行。
.box{
flex-wrap: nowrap | wrap | wrap-reverse;
}
有3个可选值:
nowrap(默认值):不换行
[外链图片转存中…(img-8JK5Ojqd-1608279639366)]
wrap:换行,第一行在上方
[外链图片转存中…(img-clFDduJ0-1608279639367)]
wrap-reverse:换行,第一行在下方
[外链图片转存中…(img-C0cmrgWs-1608279639369)]
是flex-direction属性和flex-wrap属性的简写形式,默认为row nowrap。
.box{
flex-flow: ;
}
定义了项目在主轴上的对齐方式。
.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)]
定义项目在交叉轴上如何对齐。
.box{
align-items: flex-start | flex-end | center | baseline | stretch;
}
可选值有:
[外链图片转存中…(img-29UgeEVK-1608279639378)]
定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.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)]
定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item{
order:;
}
[外链图片转存中…(img-tFHesDn6-1608279639388)]
定义项目的放大比例,默认为0,即如果存放剩余空间,也不放大。
假设默认三个项目中前两个个项目都是0,最后一个是1,最后的项目会沾满剩余所有空间。
[外链图片转存中…(img-gQ1BjdEW-1608279639390)]
假设只有第一个项目默认为0,后面两个项目flex-grow均为1,那么后两个项目平分剩余空间。
[外链图片转存中…(img-x3ScMMHY-1608279639392)]
假设第一个项目默认为0,第二个项目为flex-grow:2,最后一个项目为1,则第二个项目在放大时所占空间是最后项目的两倍。
[外链图片转存中…(img-N74Jtksk-1608279639393)]
定义了项目的缩小比例,默认为1。即如果空间不足,该项目将缩小。
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性都是0,其他项目都是1,则空间不足时,前者不缩小。负值对该属性无效。
.item{
flex-shrink:; /* 默认值1*/
}
[外链图片转存中…(img-U5ylyI1I-1608279639396)]
上图中第二个项目flex-shrink为0,所以自身不缩小。
定义了在分配多余空间之前,项目占用的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
.item{
flex-basis: | auto; /*默认auto*/
}
是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)。
允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
.item{
align-self:auto | flex-start | flex-end | center | baseline | stretch;
}
该属性可能取6个值,除了auto,其他都与align-items属性完全一致。
[外链图片转存中…(img-17Ef8Yql-1608279639398)]
使用vue create xxx
创建一个普通vue项目,然后添加vue add vuetify
插件。