一、入门
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
三大框架:
angular:模块化
react:虚拟DOM
vue:集大成者
axios:前端通信框架
jquery:dom操作频繁
解耦了
初体验
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
或者:
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
引入:<script src="../js/vue.js"></script>
输出:\{\{message}}
循环本标签:
<ul id="list">
<li v-for="movies in movies">{{movies}}</li>
</ul>
跟踪:
const 变量 let变量
<script>
const app = new Vue({
el:'list',
data:{
movies:['s','d','b']
}
})
</script>
事件
<button v-on:click="count++">+</button>
函数
<script>
const count = new Vue({
el:"#count",
data:{
count:1
},
methods:{
add:function(){
this.count++;
},
sub:function(){
this.count--;
}
}
})
</script>
使用:
<button v-on:click="add">
+
</button>
<button @click="sub">
-
</button>
MVVM
属性
el、data、methods 、computed、filter、watch
- el: string | htmlElement
- data: Object | Function
- computed
- methods: { key:string : Function }
- filter
- watch
生命周期函数
beforeCreate、created、mounted、
模板templates
缩进:2
vue + ctrl + e
属性
el属性
- 用来指示vue编译器从什么地方开始解析 vue的语法,可以说是一个占位符。
data属性
- 用来组织从view中抽象出来的属性,可以说将视图的数据抽象出来存放在data中。
template属性
- 用来设置模板,会替换页面元素,包括占位符。
methods属性
- 放置页面中的业务逻辑,js方法一般都放置在methods中
render属性
- 创建真正的Virtual Dom
computed属性
- 用来计算,有缓存
watch属性
- watch:function(new,old){}
- 监听data中数据的变化
- 两个参数,一个返回新值,一个返回旧值,
双向数据绑定
MVVM,数据发生变化,视图也变化,视图变化,数据也同步变化
响应式
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
set(newValue) {
console.log("");
value = newValue;
},
get() {
return value
}
})
})
响应情况
数组
如push、splice、=赋值(array=[1,2,3])
无法监听
array[index] = 1
object.a = 3
array.length = 5
解决
// 这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.$set(array, index, data);
上面提到的splice方法进行增删改
利用临时变量进行中转
let tempArr = [...this.targetArr]
tempArr[0] = {data: 'test'}
this.targetArr = tempArr
对象
对象的直接=赋值
this.obj = {name: 'test'}
无法监听到的
对象属性的增删改
obj: {
prop1: 'data1',
prop2: 'data2'
}
...
// 增
this.obj.prop3 = 'data3'
// 删
delete this.obj.prop1
// 改
this.obj.prop1 = 'data4'
解决
this.$set(obj, key ,value) ;//- 可实现增、改
// watch时添加deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听
this.$watch('blog', this.getCatalog, {
deep: true
// immediate: true // 是否第一次触发
});
// watch时直接监听某个key
watch: {
'obj.name'(curVal, oldVal) {
// TODO
}
}
// object.assign()+直接=赋值
this.watchObj = Object.assign({}, this.watchObj, {
name: 'xiaoyue',
age: 15,
});
二、指令
常用指令:
- v-if
- v-else-if
- v-else
- v-for
- v-on
- v-model
- v-bind
Mustache胡须
<div>
{{ }} 、 \{{stirng1 string2}}
</div>
v-once
不会跟着改变
v-html
<h2 v-html="url">
渲染出HTML标签,也会覆盖
</h2>
data:{
url:"<a href='http://www.baidu.com'>d</a>"
}
v-text
<h2 v-text="message">
会覆盖
</h2>
v-pre
不解析vue
斗篷
vue解析前不显示,vue解析后解释
<style>
[v-cloak]{
display:none;
}
</style>
<div id="app" v-clock>
{{message}}
</div>
v-bind
动态绑定值,URL等
动态绑定URL
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHref">百度</a>
简写:
<a :href="aHref">百度</a>
动态绑定class
1.
<div id="color">
<h2 class="origin" :class="{active: isActive, line: isLine}">
颜色1
</h2>
<h2 class="origin" :class="getClass()">
颜色2
</h2>
<!-- v-on -->
<button @click="butClick">
变色
</button>
</div>
<script>
const color = new Vue({
el:"#color",
data: {
isActive: true,
isLine: true
},
methods: {
butClick: function(){
this.isActive = !this.isActive;
},
getClass: function(){
return {active: this.isActive, line: this.isLine};
}
}
})
</script>
2.
<h2 clsaa="title" :class="[active, line]"> 变量数组
{{message}}
</h2>
动态绑定style
对象
<h2 :style="{fontSize:finalSize + 'px', backgroundColor: finalColor}"{{message}}</h2>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'hello',
finalSize: 100,
finalColor: 'red'
},
methods: {
getStyles: function(){
return {fontSize: this.finalSize + ‘px', backgroundColor: this.finnalColor}
}
}
})
</script>
数组
<div v-bind:style="[baseStyles, overridingStyles]">
数组类型,存入 myColor: {color: 'red'} 对象即可
</div>
v-for
<div v-for="(movie, index) in movies" :key="movie">
最好加上key,便于复用
插入:插入位置后数组后移,性能低
有key,一一对应,虚拟dom对照,没变,性能高
</div>
<div v-for="(value, key, index) in class">
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
item: [
{message: "ming"},
{message: "yue"}
]
}
})
</script>
实时更新
$set()方法重新渲染
this.$set(this.student,"age", 24)
Vue.set(this.arr, 0, 'asdf')
//this.student为你在data中声明的数组名,‘age’是你要改变的数组下的指定字段名,24是你要变化的值
v-on
事件绑定
参数
<button v-on:click="btnClick()" >按钮</button>
1.如果原方法有参数,则给第一个参数传入event参数,原方法无参数,可以直接省略括号
<button @click="btnClick">按钮</button>
修改就调用
<inpupt type="text" @input="fun()">
2.获取event参数:$event
<button @click="btnClick('aaa', $event)">按钮</button>
3.修饰符
.stop 多层触发
.prevent 阻止默认事件
.keyCode|.keyAlias 监听键盘点击 code或简写
@keyup
@keyup.enter
.native 监听组件根元素的原生事件,组件点击
.once 只触发一次回调
v-if
<h1 v-if="pl">
yes
</h1>
<h1 v-else>
no
</h1>
<h1 v-else-if="type===A">
a
</h1>
<script>
var vm = new Vue({
el: "#app",
data: {
//ok: true
type: 'A'
}
})
</script>
<div>
登录切换
虚拟dom,会出现input重用,输入框value不变
不复用: 添加key属性,不同则不复用
</div>
实例
test
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="UTF-8">
<style type="text/css">
.active{
color:blue;
}
</style>
</head>
<body>
输出:
<div id="app">
<h1>{{message}}</h1>
<h2>{{name}}</h2>
<h3 v-html="url">ddd</h3>
</div>
v-for:
<ul id="list">
<li v-for="movies in movies">{{movies}}</li>
</ul>
计数器:
<div id="count">
<h2>总数{{count}}</h2>
<button v-on:click="add">+</button>
<button @click="sub">-</button>
</div>
变色:
<div id="color">
<h2 :class="{active: isActive, line: isLine}">
颜色
</h2>
<h2 class="origin" :class="getClass()">
颜色2
</h2>
<!-- v-on -->
<button @click="butClick">
变色
</button>
</div>
<script src="../js/vue.js"></script>
<script>
//let变量 const变量
const app = new Vue({
el:'#app', //挂载元素
data:{ //定义数据
message:'你好!vue!',
name:'vue',
url:"<a href='http://www.baidu.com'>d</a>"
}
})
const list = new Vue({
el:'#list',
data:{
movies:['s','d','b']
}
})
const counter = new Vue({
el:"#count",
data:{
count:1,
},
methods:{
add:function(){
this.count++;
},
sub:function(){
this.count--;
}
}
})
const color = new Vue({
el:"#color",
data: {
isActive: true,
isLine: true
},
methods: {
butClick: function(){
this.isActive = !this.isActive;
},
/*
getClass: function(){
return {active: this.isActive, line: this.isLine};
}*/
getClass(){
return {active: this.isActive, line: this.isLine};
}
}
})
</script>
</body>
</html>
点击变色
<ul id="myTag">
<li v-for="(movie,index) in movies" :class="getClass(index)" @click="changeColor(index)">
{{index}}--{{movie}}
</li>
</ul>
<script>
const tag = new Vue({
el: "#myTag",
data: {
movies:["11", "22", "33", "44"],
isblue: [false, true, true, false],
isyellow: false
},
methods: {
changeColor: function(index){
this.$set(this.isblue, index, !(this.isblue[index]));
},
getClass: function(index){
return {blue: this.isblue[index], yellow: this.isyellow };
}
}
})
</script>
v-model
忽略所有表单元素的value、checked、selected特性的初始值,总是将vue实例的数据作为数据来源
在ios中,用户无法选择第一个选项
v-model即可实现互斥,不需name,可以根据值默认选中
修饰符
- lazy:
- 失去焦点或者回车才同步
- number:
- 数字类型
- trim:
- 去除空格
实现v-model
<div id="app">
<input type="text" :value="message" @input="valueChange">
<input type="text" :value="message" @input=" message = $event.target.value ">
<h2>
{{message}}
</h2>
</div>
<script>
const app = new Vue({
el: "#app",
data:{
message: "你好"
},
methods: {
valueChange(event){
this.message = event.target.value;
}
}
})
</script>
使用
1.text
<div id="app">
<input type="text" v-model="message" />
{{message}}
</div>
<script>
var data = new Vue({
el: "#app",
data: {
message: "234"
}
})
</script>
2.radio
<div id="app">
<input type="radio" name="sex" value="男" v-model="value" />男
<input type="radio" name="sex" value="女" v-model="value" />女
选中了:{{value}}
</div>
<script>
var data = new Vue({
el: "#app",
data: {
value: ""
}
})
</script>
3.selected
<div v-model="selected">
<seclet>
<option value="" disabled>--请选择--</option>
<option>A</option>
<option>B</option>
<option>C</option>
</seclet>
<span>value:{{selected}}</span>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
selected: ''
}
});
</script>
v-show
display
三、语法
计算属性
解析
存在缓存,比使用方法的性能高
将不经常变化的计算结果进行缓存,节省系统开销
例子:
{{compute}}
<script>
computed:{
fullName: function(){
return this.name+ ' '+ this.yourname;
}
}
</script>
语法
<script>
computed:{
fullName: {
//一般不写,使用:app.fullName = "asdf asdf";
set: function(newValues){
console.log(newValue);
const names = newValues.split(' ');
this.name = name[0];
this.yourname = name[1];
},
//一般简化
get: function(){
return this.name+ ' '+ this.yourname;
}
},
// 简化
getName(){
}
}
</script>
ES6
var、const、let
let存在缺陷
ES5之前: if与for没有块级作用域,共用循环i, for,使用闭包,函数存在作用域
ES5:闭包
for(var i = 0; i < btns.length; i++){
(function(i){
btn[i].addEventListener('click', function(){
console.log("i");
})
})(i);
/*函数中有自己的i,作用域
(function(){})(i);
*/
}
ES6 : let / const let存在自己的作用域,const常量
const btns = documents.getElementsByTagName('button');
//如果是var,只会是5
for(let i = 0; i < btns.length ; i++){
btns[i].addEventListener('click', function(){
console.log("点击"+ i);
})
}
const的对象不能修改,但是属性可以修改
对象增强写法
//1.属性增强方法
let name = "myname";
//直接赋值
let obj = {
name
}
//2.方法增强
let obj = {
run(){
console("ES6");
}
}
//未增强
let obj = {
run: function(){
}
}
两空格
箭头函数
const sum = (num1, num2) => {
return num1 + num2;
}
const power = num => {
return num * num;
}
const mul = (num1, num2) => num1 + num2;
箭头函数中的this 引用最近作用域的this
字符串
str.startsWith("")
.endWith("")
.includes("");
字符串模板
var str = `${age}你好`
解构
var test = ( {name} ) => console.log("hello", name)
test( {name: "sdf"} );
对象优化
Object.keys(person) // array[]
Object.values(person) // arrays[]
Object.entries(person) /* [
[ "name", "mingyue" ],
]*/
Object.assign(origin, newObj1) // 赋值到origin中
数组响应式
下标修改无法响应
pop() 删除最后一个
shift() 删除第一个
unshift() 第一个添加
push() 最后一个添加
splice() start, [length|0, [‘a’, ‘b’, ‘c’] ]
reverse()
filter
// 全局
Vue.filters("showValue", function(val) {
})
// 局部
filters: {
showValue(value){
return "¥" + value.toFixed(2);
}
}
{{sumValue | showValue}}
购物车
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./vue.js"></script>
</head>
<body>
<div id = "table">
<table border v-if="books.length">
<tr>
<td>id</td>
<td>书名</td>
<td>价格</td>
<td>数量</td>
<td>操作</td>
</tr>
<tr v-for="(book,index) in books">
<td>{{book.id}}</td>
<td>{{book.name}}</td>
<td>{{book.value | showValue}}</td>
<td>{{book.num}}</td>
<td>
<button @click="addBookNum(index)">添加</button>
<button @click="subBookNum(index)">减少</button>
<button @click="remBook(index)">移除</button>
</td>
</tr>
<tr>
<td colspan="5">合计:{{sumValue | showValue}}</td>
</tr>
</table>
<h2 v-else>
购物车为空
</h2>
</div>
<script>
var app = new Vue({
el: "#table",
data: {
books:[
{
id: 1,
name: "《算法1》",
value: 85.10,
num: 1
},
{
id: 2,
name: "《算法2》",
value: 65,
num: 1
},
{
id: 3,
name: "《算法3》",
value: 95,
num: 1
}
]
},
computed: {
sumValue(){
return this.books.reduce((preTotal, book) => {
return preTotal + book.value *book.num;
}, 0);
//let sum = 0;
//for(let index = 0; index < this.books.length; index++){
/*
for(let index in this.books){
let book = this.books[index];
sum += book.value * book.num;
}
for(let book of this.books){
sum += book.value * book.num;
}
return sum;
*/
}
},
methods: {
remBook(index){
this.books.splice(index, 1);
},
subBookNum(index){
if(this.books[index].num > 1){
this.books[index].num--;
}
else{
this.remBook(index);
}
},
addBookNum(index){
this.books[index].num++;
}
},
filters: {
showValue(value){
return "¥" + value.toFixed(2);
}
}
})
</script>
</body>
</html>
获取配置值
process.env.BASE_API
h()渲染
h() 函数是一个用于创建 VNode 的实用程序
// @returns {VNode}
h(
// {String | Object | Function} tag
// 一个 HTML 标签名、一个组件、一个异步组件、或
// 一个函数式组件。
//
// 必需的。
'div',
// {Object} props
// 与 attribute、prop 和事件相对应的对象。
// 这会在模板中用到。
//
// 可选的(在开发时。建议传,实在没有传的时候,传入 null)
{},
// {String | Array | Object} children
// 子 VNodes, 使用 `h()` 构建,
// 或使用字符串获取 "文本 VNode" 或者
// 有插槽的对象。
//
// 可选的。
[
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
四、vue组件
组件
基础
可复用的Vue实例,JSTL的自定义标签
<div id="app">
<mingming></mingming>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component("mingming",{
template: '<li>hello</li>'
});
var vm = new Vue({
el: "#app"
});
</script>
2.通过v-bind将item作为参数name的值传入props
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<mingming v-for="(item,index) in items" v-bind:name="item" :yindex="index"></mingming>
</div>
<script>
Vue.component("mingming",{
props: ['name', 'yindex'],
template: '<li>{{yindex}}{{name}}</li>'
});
var vm = new Vue({
el: "#app",
data: {
items: ["ming", "fang", "love"]
}
});
</script>
script标签写html
<script type="text/x-template" id="cpn">
<div>
<h1>标题</h1>
</div>
</script>
template: "#cpn"
template标签写html
<template id = "cpn">
<div>
<h1>标题</h1>
</div>
</template>
数据通信
<script>
Vue.component("mingming",{
template: '<li>{{yindex}}{{name}}</li>',
data(){
return {
title: 'mingyue'
}
}
});
var vm = new Vue({
el: "#app",
data: {
}
});
</script>
slot
slot中定义name,使用替换时使用
slot=name
进行替换父中使用
slot-scope
接受子组件传递的值,子中使用:name=
进行传递
基础
<div id="app">
<cpn></cpn>
<cpn><i>一对多,替换全部无name的slot</i></cpn>
</div>
<template id="cpn">
<div>
组件
</div>
<slot>
<button>
默认显示
</button>
</slot>
</template>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<todo>
<todo-title slot="todo-title" :title="title"></todo-title>
<todo-items slot="todo-items" v-for="item in todoItems" :item="item"></todo-items>
</todo>
</div>
<script type="text/javascript">
Vue.component("todo", {
template: '<div>\
<slot name="todo-title"></slot>\
<ul>\
<slot name="todo-items"></slot>\
</ul>\
</div>'
});
Vue.component("todo-title", {
props: ['title'],
template: '<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['item'],
template: '<li>{{item}}</li>'
});
var app = new Vue({
el: "#app",
data: {
title: "mingming",
todoItems: ["mingfu", "fangfang", "love"]
}
})
</script>
</body>
</html>
slot作用域
<div id="app">
<cpn>
<template slot-scope="slot">
使用组件中的值
<span v-for="item in slot.data">{{item}} </span>
<span>{{slot.data.join("-")}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="languages">
将子组件中的language传到data中,便于父组件调用
<ul>
<li v-for="item in languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
删除
this.$emit(functionName, params)
给父组件发送事件
v-on:newFunName="funName(param)"
splice(index, num)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<todo>
<todo-title slot="todo-title" :title="title"></todo-title>
<todo-items slot="todo-items" v-for="(item, index) in todoItems" :item="item" :index="index" v-on:remove="removeItem(index)"></todo-items>
</todo>
</div>
<script type="text/javascript">
Vue.component("todo", {
template: '<div>\
<slot name="todo-title"></slot>\
<ul>\
<slot name="todo-items"></slot>\
</ul>\
</div>'
});
Vue.component("todo-title", {
props: ['title'],
template: '<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['item', 'index'],
template: '<li>{{index}}---{{item}}---<button v-on:click="remove(index)">删除</button></li>',
methods: {
remove: function(index){
this.$emit('remove' , index);
}
}
});
var app = new Vue({
el: "#app",
data: {
title: "mingming",
todoItems: ["mingfu", "fangfang", "love"]
},
methods: {
removeItem: function(index){
alert("vue" +this.todoItems[index]);
this.todoItems.splice(index, 1);
}
}
})
</script>
</body>
</html>
props
给子组件传值
itmeItem -> item-item 大写有错
{{item}}
const cpn = {
template: "#cpn",
// 1, 参数名称
//props: ['item'],
/* //2,限制类型
props: {
item: Array
},
*/
// 3, 默认值
props: {
item: {
type: String,
default: "none",
required: true
}
// 数组,对象
item: {
type: Array,
default(){
return [1,2,3]
}
}
}
}
this.$emit
父子通信
props、this.$emit(function, params)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./vue.js"></script>
</head>
<body>
<div id = "first">
<component1 :value1="value1" @changevalue1="changeValue1"></component1>
<div>
父中的值{{value1}}
</div>
</div>
<template id="component1">
<div>
输入框1:<input type="text" v-model="cvalue" @input="changeCValue">
{{cvalue}}
</div>
</template>
<script>
var app = new Vue({
el: "#first",
data: {
value1: 1
},
methods: {
changeValue1(value1){
this.value1 = value1;
}
},
components: {
component1: {
template: "#component1",
props:{
value1: [Number, String]
},
data(){
return {
cvalue: this.value1
}
},
methods:{
changeCValue(event){
// 将触发父组件中的changevalue1事件,触发后使用changeValue1函数进行执行
this.$emit("changevalue1", this.cvalue);
}
}
}
}
});
</script>
</body>
</html>
父子访问
父访问子
this.$children[0] 获取数组第一个
$refs reference引用,对象
<component1 ref = "a"></component>
this.$ref.a
子访问父
this.$parent
访问root
this.$root
引入css
<style>
@import "./assets/css/base.css";
</style>
Vue.nextTick
https://segmentfault.com/a/1190000012861862
响应式数据改变后无法立刻获取到dom,可以使用Vue.nextTick延迟获取
五、Axios
初见
开源的主要作用实现ajax异步通信
引入:
npm install axios --save
idea更改es6
jquery对Dom操作频繁
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="vue" v-cloak>
{{info.name}}<br>
<a :href="info.url">点我</a>
{{info.items[0]}}
</div>
<script>
window.onload = function(){
var vm = new Vue({
el: "#vue",
data(){
return{
info:{
name: null,
url: null,
items: null
}
}
},
mounted(){
axios.get("./json/data.json").then(response => (this.info = response.data));
}
});
axios({
method: "get",
url: "./data.json",
responseType: 'stream'
}).then(function(response){
console.log(response);
});
}
</script>
ajax
<script>
$.ajax({
type: "get",
url: "./test.html"
data: $("form").serialize(),
datatype: "json",
success: function(){
}
})
</script>
axios
import axios from 'axios'
Vue.prototype.$axios = axios;
vue.config.js
module.exports = {
devServe: {
open: true,
host: 'localhost',
port: 8080,
https: false,
hotOnly: false,
proxy: {
// 配置跨域
'/api': {
target: 'https://ele-interface.herokuapp.com/api/',
ws: true, //如果要代理 websockets,配置这个参数
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, //是否跨域
pathRewrite:{
'^/api':''
}
}
}
}
}
请求
请求方式
- axios(config)
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
axios({
//url: "http://123.207.32.32:8000/home/multidata",
//url: "@/assets/data.json",
url: "http://123.207.32.32:8000/home/data?type=sell&page=1",
params: {
type: "sell",
page: 1
},
method: 'get'
}).then((data) => {
console.log(data);
})
配置
- url
- method
- baseURL
- transformRequest:[function(data){}] 请求前数据处理
- transformResponse: [function(data){}] 请求后的数据处理
- headers: {} 自定义的请求头
- params: {} url查询对象
- paramsSerializer: function(params){} 查询对象序列化参数
- data: {key: ‘aa’} request body
- timeout: 100 超时设置
- withCredentials: false, 跨域是否携带Token
- adapter: function(resolve, reject, config){} 自定义请求处理
- auth: {name: “ming”} 身份验证信息
- responseType: ‘json’ 响应数据格式 json/blob/document/arraybuffer/text/stream
并发请求
axios.all([
axios({
}), axios({
})])/*.then(results => { // 1. results[0]
})*/.then(axios.spread( (result1, result2) => { // 2. 将结果分离
}))
全局配置
axios.defaults.baseURL = "";
axios.defaults.timeout = 5000;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios({
url: "/home" //自动携带baseURL
})
分类配置
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8080',
timeout: 5000
});
instance1({
url: '/home/multidata',
params: {
}
}).then(res => {
console.log(res);
})
封装
避免更改框架,每个组件都引入同一个框架,修改麻烦,将框架进行封装,只需要改一个地方
request01.js
可以直接传入回调函数,请求完成后执行回调函数
可以使用promise使用resolve返回数据
import axios from 'axios';
export function request(config) {
return new Promise((resolve, reject) => {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8080',
timeout: 5000
})
// 1. resove,多余
instance(config)
.then(res => {
resovle(res);
})
.catch(err => {
reject(err);
})
})
// 2. instance(config)返回promise
const instance = axios.create({
baseURL: 'http://123.207.32.32:8080',
timeout: 5000
})
return instance(config);
}
使用
request({
url: ""
}).then(res => {
}).catch(err => {
})
拦截器
请求拦截
const instance = axios.create({
baseURL: 'http://123.207.32.32:8080',
timeout: 5000
})
instance.interceptors.request.use(config => {
console.log(config); // 获取到传入的config
// 1. 过滤config信息
// 2. 请求图标
return config; // 需要交还config
}, err => {
console.log(err);
})
响应拦截
const instance = axios.create({
baseURL: 'http://123.207.32.32:8080',
timeout: 5000
})
instance.interceptors.response.use(res => {
console.log(res);
return res.data; // 只返回data
}, err => {
console.log(err);
})
跨域
同源:域名、协议、端口均相同
六、vue-cli
vue-cli官方提供的脚手架,快速生成一个vue项目模板。
command-line interface 命令行界面
预先定义好的目录结构以及基础代码
功能:
- 同一目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
npm
node -v
npm -v
淘宝镜像加速器
npm install cnpm -g
#npm更换淘宝镜像
npm config set registry https://registry.npm.taobao.org
vue-cli
cnpm install -g @vue/cli
vue --version
npm install @vue/cli-init -g 2.x版本
npm clean cache --force
cnpm install vue-cli -g
#是否成功
vue list
vue cli2
vue init webpack mingyue
npm run dev
npm run build
vue cli3
vue create my-project
npm run serve
vue ui // 进行配置
ES-lint js规范
项目下
npm install
npm audit fix
npm run dev
dist
webpack
ES6打包成ES5
npm install webpack@3.6.0 -g [ --save-dev 本地安装-开发时依赖]
webpack src/main.js dist/bundle.js 》全局
webpack ./src/main.js --output-filename ./dist/bundle.js --output-path . --mode development
cnpm
npm install webpack -g
npm install webpack-cli -g
webpack -v
webpack-cli -v
演变
模块化
解决变量冲突,代码不可复用(使用闭包解决变量冲突)
代码复用:
var module1 = (function(){
var obj = {};
obj.name = "ming";
obj.sum = function(a, b){
return a+b;
}
return obj;
})()
<script src=""></script>
commonJs
require("modle");
require("../module.js");
export.doStuff = function(){};
module.exports = someValue;
// 导出
module.exports = {
flag, sum
}
// 导入
var {flag, sum} = require('./aaa.js');
require("./css.css"); // 需要loader
// npm install --save-dev css-loader
缺点:
同步加载,阻塞加载
AMD
define("module", ["dep1", "dep2"], function(d1, d2)){
retrun someExportedValue;
}
异步加载模块,并行加载多个模块
缺点:提高开发成本
CMD
SPM打包,模块加载逻辑偏重
ES6模块
编译时就能确定模块的依赖关系
import "jquery";
export function doStuff(){}
module "localModule" {}
// 导出
export var a = 100;
export {
flag, sum
}
export class Person{
constructor(name, age){
this.name = name;
}
run(){
}
}
// default重命名,只能有一个
export default var name = "ming";
import myName from "./sdf.js";
// 导入
import {flag} from "./sdf.js";
import * as all from "./sdf.js";
<script type="module" src=""></script>
优点
容易静态分析
面向未来的EcmaScript标准
缺点
原生浏览器端没有实现该标准
只有新版的Nodejs才支持
尝试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="dist/js/bundle.js"></script>
</body>
</html>
原理
–save-dev 开发时依赖
–save 开发、发布都依赖
基础
- css-loader/style-loader
- less-loader/less
- url-loaer/file-loader
- babel-loader
- vue-loader
- webpack-dev-server
- webpack-merge
main.js
const {add} = require('./math.js');
console.log(add(1, 2));
math.js
function add(num1, num2){
return num1 + num2;
}
module.exports = {
add
}
npm init 初始化项目,生成package.json文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
}
npm install webpack@3.6.0 --save-dev
npm install
webpackage.config.js
<script>
const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
//publicPath: 'dist/' // url自动添加
},
module: {
rules: [
{
test: /\.css$/,
// 只负责加载,不负责解析生效,
// 添加dom, 从右到左
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: "images/[name][hash:8].[ext]"
}
}
]
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: "images/[name][hash:8].[ext]"
}
}
]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
}
}
};
</script>
npm run build即可运行
npm install --save-dev css-loader // 能导入css
npm install --save-dev style-loader // 解析css
url-loader
jpg: 转化为base64
npm install --save-dev url-loader
file-loader
npm install --save-dev file-loader
babel
npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env webpack
vue
npm install vue --save
import Vue from 'vue' export default
runtime-only 代码中不能有template
runtime-compiler
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
}
}
npm install vue-loader vue-template-compiler --save-dev
import Vue from 'vue';
import App from "./App.vue";
new Vue({
el: '#app',
template: "<app></app>",
components: {
App
}
});
<template>
<div id="example">{{message}}</div>
</template>
<script>
export default {
data(){
return {
message: 'Hello Vue!'
};
}
}
</script>
<style>
#example{
color: red;
font-size: 50px;
}
</style>
"vue-loader": "^13.0.0",
npm install 更新
{
test: /\.vue$/,
use: ['vue-loader']
}
html
npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
es6转化es5
babel
vue
npm install vue --save
尝试
npm、webpack、
import Vue from 'vue';
var app = {
template: "<div>{{message}}</div>",
data(){
return {
message: 'Hello Vue!'
};
}
}
new Vue({
el: '#app',
template: "<app></app>",
components: {
app
}
});
plugin
版权
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.BannerPlugin("版权所有");
]
}
html
npm install html-webpack-plugin --save-dev 版本不对!
npm install html-webpack-plugin@3.2.0 --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
压缩
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
const path = require('path');
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
//publicPath: 'dist/' // url自动添加
},
module: {
rules: [
{
test: /\.css$/,
// 只负责加载,不负责解析生效,
// 添加dom, 从右到左
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: "images/[name][hash:8].[ext]"
}
}
]
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: "images/[name][hash:8].[ext]"
}
}
]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
}
},
plugins: [
new webpack.BannerPlugin("版权所有"),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new UglifyjsWebpackPlugin()
]
};
服务器
npm install --save-dev webpack-dev-server@2.9.1
config
devServer: {
contentBase: './dist',
inline: true
}
package
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server --open"
},
插件分离
npm install webpack-merge --save-dev
"webpack-merge": "^4.0.0" 版本问题
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --config ./build/dev.config.js"
}
dev.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: './dist',
inline: true
}
});
prod.config.js
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin()
]
})
路径
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'), // 更改
filename: 'bundle.js',
//publicPath: 'dist/' // url自动添加
}
}
render
差距
runtime+compile
runtime
new Vue({
render: function(createElement){
// 标签,属性,内容
return createElement('h2',
{class: 'box'},
['hello world',createElement('button', ['按钮'])]);
}
})
var myApp = {
template: '<div>{{name}} myvue</div>',
data(){
return {
name: 'hello!'
}
}
}
/* eslint-disable no-new */
new Vue({
el: '#app',
render: (c => c(myApp))
})
vue-cli3
配置
vue ui
vue.config.js
module.exports = {
}
七、vue-route
简介
后端渲染: 服务解析地址映射,使用jsp等
前端渲染:ajax发送请求,将数据渲染
spa页面:单页面 一次全部加载html + css +js :映射关系vue - router
location.hash = “aaa” 网页不进行刷新请求
history.pushState({}, ‘’, “home”) 栈
history.back() history.go(-1)
history.forward()
history.repalceState({}, ‘’, ‘about’) 无法返回
安装
npm install vue-router –save -dev
npm audit fix
routes添加路由规则,router实例中添加routers,将router加入Vue
new Vue({router}) <------ new VueRouter(router) <-------- routes
import VueRouter from 'vue-router'
import Vue from "vue";
Vue.use(VueRouter);
const routers = [
];
const router = new VueRouter({
routers
})
export default router;
使用
App.vue
使用router-link
和router-view
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
main.js
使用router
import Vue from 'vue'
import App from './App'
import router from './router';
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
render: (c => c(App))
})
router/index.js
注册路由
redirect: “/home”
mode: “history” 不使用hash
import VueRouter from "vue-router";
import Vue from "vue";
import Home from "../components/Home";
import About from "../components/About"
// 1.使用Vue.use(插件),安装插件
Vue.use(VueRouter);
const routes = [
{
path: '/',
redirect: "/home"
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
];
const router = new VueRouter({
routes,
mode: "history",
linkActiveClass: 'active' // 修改活跃属性名
})
export default router;
实例
router-link
属性
- tag 渲染成其他标签
- replace 无法返回
- active-class 修改活跃属性名
携带参数
<router-link :to="'/user/' + userId">将data中的userId替换</router-link>
{
path: '/user/:userId',
component: user
}
获取参数
computed: {
userId(){
return this.$route.params.userId; // router是大的,这个是route
}
}
跳转
this.$router.push(‘/home’)
this.$router.replace()
懒加载
打包构建js会很大,一次加载完影响体验
使用到的组件再加载
const Home = () => import("../components/Home");
const About = () => import("../components/About");
const routes = [
{
path: '/',
redirect: "/home"
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
];
const router = new VueRouter({
routes,
mode: "history"
})
export default router;
嵌套路由
定义路由添加children
const Home = () => import("../components/Home");
const About = () => import("../components/About");
const HomeNews = () => import("../components/News");
const HomeMessages = () => import("../components/Messages");
{
path: '/home',
component: Home,
children: [
{
path: '',
redirect: 'news'
},
{
path: 'news',
component: HomeNews
},
{
path: 'messages',
component: HomeMessages
}
]
}
传值
params
localhost:8080/home/messages/234
携带参数
<router-link :to="'/user/' + userId">将data中的userId替换</router-link>
{
path: '/user/:userId',
component: user
}
获取参数
computed: {
userId(){
return this.$route.params.userId; // router是大的,这个是route
}
}
querys
localhost:8080/home/messages?id=234
<router-link :to="{path: '/profile', query: {name: 'mingyue'}}">档案</router-link>
获取
this.$route.push({
path: '/profile',
query: {
name: 'mingyue'
}
})
继承
Vue.prototype.name = “mingyue”
所有组件都会有这个属性
更改title
created
created(){
document.title("首页") ;
}
beforeEach
前置钩子
const routes = [
{
path: '/',
redirect: "/home"
},
{
path: '/home',
component: Home,
meta: {
title: "首页"
}
}
];
router.beforeEach((to, from, next) => {
document.title = to.matched[0].meta.title;
console.log(to);
next();
})
router.beforeEach((to, from, next) => {
const isLogin = localStorage.ele_login ? true: false;
if (to.path == '/login') {
next();
}else {
isLogin? next(): next('/login');
}
afterEach
后置钩子
router.afterEach((to, from) =>{
console.log("---");
})
路由独享守卫
beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
}
}
]
})
组件内守卫
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`
}
}
keep-alive
属性函数
- created
- destroyed
- activated 使用keep-alive有效
- deactivated 使用keep-alive有效
- beforeRouteLeave
keep-alive
属性
- include 这些组件使用keep-alive
- exclude 不使用
<keep-alive>
<router-view></router-view>
</keep-alive>
<keep-alive exclude="About,Profile">
<router-view></router-view>
</keep-alive>
export default {
name: 'Home',
data() {
return {
url: "/home/news"
}
},
activated() {
if(this.$route.path != this.url)
this.$router.push(this.url);
}
,
beforeRouteLeave(to, from, next){
this.url = this.$route.path; // 通过该路由获取路由路径
next();
}
}
小案例:导航栏
使用@来定位到src目录
router/index.js
注册路由
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const Home = () => import("../views/home/Home.vue");
const Sort = () => import("../views/sort/Sort.vue");
const ShopCar = () => import("../views/shopcar/ShopCar.vue");
const Profile = () => import("../views/profile/Profile.vue");
const routes = [
{
path: "/",
redirect: "/home"
},
{
path: "/home",
component: Home
},
{
path: "/sort",
component: Sort
},
{
path: "/shopcar",
component: ShopCar
},
{
path: "/profile",
component: Profile
}
]
const router = new VueRouter({
routes,
mode: "history"
})
export default router;
main.js
使用路由
import Vue from 'vue'
import App from './App'
import router from './router';
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
render: h => h(App)
})
App.vue
使用组件、插入插槽、传递参数
<template>
<div id="app" >
<tar-bar>
<tar-bar-item path="/home" color="blue">
<img slot="item-icon" src="./assets/img/tabbar/home03.png" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/home02.png" alt="">
<div slot="item-text">首页</div>
</tar-bar-item>
<tar-bar-item path="/sort">
<img slot="item-icon" src="./assets/img/tabbar/sort01.png" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/sort02.png" alt="">
<div slot="item-text">分类</div>
</tar-bar-item>
<tar-bar-item path="/shopcar">
<img slot="item-icon" src="./assets/img/tabbar/shopcar01.png" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/shopcar02.png" alt="">
<div slot="item-text">购物车</div>
</tar-bar-item>
<tar-bar-item path="/profile">
<img slot="item-icon" src="./assets/img/tabbar/profile01.png" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/profile02.png" alt="">
<div slot="item-text">我的</div>
</tar-bar-item>
</tar-bar>
<router-view></router-view>
</div>
</template>
<script>
import router from "./router"
import TarBar from "./components/tabbar/TabBar.vue"
import TarBarItem from "./components/tabbar/TabBarItem.vue"
export default {
name: 'App',
router,
components: {
TarBar,
TarBarItem
}
}
</script>
<style>
@import "./assets/css/base.css";
</style>
TabBar.vue
导航栏slot,准备好导航条样式
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'TabBar',
components: {
}
}
</script>
<style>
#tab-bar{
display: flex;
height: 49px;
background: rgb(235, 231, 231);
position: fixed;
bottom: 0px;
left: 0px;
right: 0px;
}
</style>
TabBarItem.vue
导航条中的各项,样式,准备好图片和文字的插槽,接收参数path和color,点击组件后this.$router.repalce(this.path)
通过path,判断是否活跃this.$route.path.indexOf(this.path) !== -1
<template>
<div class="tab-bar-item" @click="clickItem" >
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="textColor"><slot name="item-text"></slot></div>
</div>
</template>
<script>
export default {
name: 'TabBarItem',
props: {
path: String,
color: {
type: String,
default: "red"
}
},
components: {
},
data() {
return {
}
},
computed: {
isActive(){
return this.$route.path.indexOf(this.path) !== -1;
},
textColor(){
return this.isActive? {color: this.color} : {};
}
},
methods: {
clickItem(){
if(this.path != this.$route.path)
this.$router.replace(this.path);
}
}
}
</script>
<style>
.tab-bar-item{
font-size: 13px;
flex: 1;
text-align: center;
box-shadow: 0 -1px 3px rgba(235, 231, 231, 1);
padding-top: 3px;
cursor: pointer;
}
.tab-bar-item:hover{
background-color: #ccc;
}
.tab-bar-item img{
width: 29px;
height: 29px;
vertical-align: middle; /* 能清除margin-bottom */
}
</style>
$router和$route
$router为 VueRouter 实例,是路由操作对象,只写对象,想要导航到不同url,则使用router.push方法
$route为当前router跳转对象,路由信息对象,只读对象,里面可以获取name、path、query、params等
八、Element
网址
https://element.eleme.cn/#/zh-CN/component/installation
安装
npm i element-ui -S
CDN
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
开始
vue init webpack hello-vue
# 进入文件
# 安装 vue-router
npm install vue-router --save-dev
# element-ui
npm i element-ui -S
# 安装依赖
npm install
# 安装sass加载器
cnpm install sass-loader node-sass --save-dev
#启动
npm run dev
上手
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
"sass-loader": "^8.0.2",
npm install
docsify文档
实例一
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Main from '../views/Main'
import Login from '../views/Login'
Vue.use(Router);
export default new Router({
routes:[
{
path: '/main',
name: 'main',
component: Main
},
{
path: '/login',
component: Login
}
]
})
Login.vue
<template>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
age: ''
},
rules: {
pass: [
{ validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
age: [
{ validator: checkAge, trigger: 'blur' }
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
this.$router.push("/main");
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
Main.vue
<template>
<h1>首页</h1>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped>
</style>
App.vue
<template>
<div id="app">
<h1>界面</h1>
<router-link to="/main">main</router-link>
<router-link to="/login">login</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
</style>
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Main from '../views/Main'
import Login from '../views/Login'
Vue.use(Router);
export default new Router({
routes:[
{
path: '/main',
name: 'main',
component: Main
},
{
path: '/login',
component: Login
}
]
})
实例二:导航栏
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Main from '../views/Main'
import Login from '../views/Login'
import UserList from '../views/User/List'
import UserProfile from '../views/User/Profile'
Vue.use(Router);
export default new Router({
routes:[
{
path: '/main',
component: Main,
children: [
{
path: '/user/profile',
component: UserProfile
},
{
path: '/user/list',
component: UserList
}
]
},
{
path: '/login',
component: Login
}
]
})
main.vue
<template>
<el-row class="tac">
<el-col :span="12">
<h5>自定义颜色</h5>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<span slot="title">分组一</span>
<router-link to="/user/profile"><el-menu-item index="1-1">用户资料</el-menu-item></router-link>
<router-link to="/user/list"> <el-menu-item index="1-2">用户列表</el-menu-item></router-link>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-col>
<el-col :span="12">
<router-view></router-view>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'Main',
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
}
}
</script>
传参
<script>
export default new Router({
routes:[
{
path: '/main',
component: Main,
children: [
{
path: '/user/profile/:id',
name: 'userProfile',
component: UserProfile //,props: true 解耦
},
{
path: '/user/list',
component: UserList
}
]
},
{
path: '/login',
component: Login
}
]
})
</script>
<router-link :to="{name: 'userProfile',params: {id: 1}}"><el-menu-item index="1-1">用户资料</el-menu-item></router-link>
<h2>{{ $route.params.id }}</h2>
<!--
props解耦 输出用 {{id}}
-->
<script>
export default{
props: ['id'],
name: 'userProfile'
}
</script>
重定向
<script>
export default new Router({
routes:[
{
path: '/main',
component: Main,
}
{
path: '/goHome',
redirect: '/main'
}
]
})
</script>
登录
this.$router.push("/main/"+ this.ruleForm.pass);
模式
不用#
<script>
export default new Router({
mode: 'history',
routes:[
{
path: '/main',
component: Main,
}
{
path: '/goHome',
redirect: '/main'
}
]
})
</script>
404
<template>
<div><h1>404,页面不见了</h1></div>
</template>
<script>
export default {
name: "NotFound"
}
</script>
<style scoped>
</style>
<script>
import NotFound from '../views/NotFound'
export default new Router({
mode: 'history',
routes:[
{
path: '/main/:pass',
component: Main,
props: true,
},
{
path: '/login',
component: Login
},
{
path: '*',
component: NotFound
}
]
})
</script>
<script>
export default new Router({
mode: 'history',
routes:[
{
path: '/main',
component: Main,
}
{
path: '/goHome',
redirect: '/main'
}
]
})
</script>
<script>
export default new Router({
mode: 'history',
routes:[
{
path: '/main',
component: Main,
}
{
path: '/goHome',
redirect: '/main'
}
]
})
</script>
钩子
- to: 路由要跳转的路径信息
- from:跳转前的路径信息
- next:路由控制参数
- next() 跳入下一个页面
- next(‘/path’) 改变路由的跳转方向
- next(false) 返回原来的页面
- next((vm) => {}) 仅在beforeRouteEnter中可用,vm是组件实例
<script>
export default {
props: ['id'],
name: "UserProfile",
beforeRouteEnter: (to, from, next)=>{
console.log("进入之前");
next();
},
beforeRouteLeave: (to, from, next)=>{
console.log("离开之前");
next();
}
}
</script>
钩子中使用异步请求
安装
cnpm install --save axios vue-axios
<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
</script>
<script>
export default {
props: ['id'],
name: "UserProfile",
beforeRouteEnter: (to, from, next)=>{
console.log("进入之前");
next(vm =>{
vm.getData();//进入路由之前执行该方法
});
},
beforeRouteLeave: (to, from, next)=>{
console.log("离开之前");
next();
},
methods: {
getData: function(){
this.axios({
method: 'get',
url: 'http://localhost:8080/static/mock/data.json',
}).then(function(response){
console.log(response);
})
}
}
}
</script>
九、Vuex
入门
Vuex是专为Vue.js应用程序开发的状态管理模式,集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式变化
组件同享变量,响应式
如:
登录状态、用户名称、头像、地理位置
商品收藏、购物车物品
devtools可以跟踪状态信息,但是异步不能用,使用actions
npm install vuex --save
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
token: "abcdef",
counter: 1
},
// 修改,方法中使用this.$store.commit('increase');
mutations: {
// 变化
increase(state) {
// 直接添加的属性不会响应式
// Vue.set(state.info, 'address', '广西');
// delete state.info.address 删除属性无法响应
// Vue.delete(state.info, 'age');
state.counter++;
},
// 用变量代替名字
/*
[INCREASE](state){
}
*/
decrease(state) {
state.counter--;
},
increaseCount(state, counter) {
state.counter = state.counter + counter;
}
},
// 代替mutations,异步操作,必须通过mutations
actions: {
// 上下文, 参数
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updateInfo'); // 调用mutation中的方法
}, 1000)
}
// 使用this.$store.dispatch('aUpdateInfo', 'cannshu');
// 1. 想知道修改完成? payload传入对象中有函数,commit之后运行函数即可
// 2.使用new Promise((resolve, reject) => { resolve(""); })
bUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo'); // 调用mutation中的方法
resolve("success");
}, 1000)
})
}
/*
// 调用actions调用mutations,进行分配
this.$store.dispatch("bUpdateInfo", 'infoaaa').then( (res) => {
conosle.log(res);
})
*/
},
// 对数据需要变化再拿,{{$store.state.powerCounter}}
getters: {
powerCounter(state) {
return state.counter * state.statecount;
},
more20stu(state) {
return state.students.filter( stu => stu.age > 20 )
},
// 使用已有的getters
more20stuLength(state, getters) {
return getters.more20stu.length; // 获取长度
},
// 传值
moreAgeStu(state) {
/*
return function (age){
return state.students.filter( stu => stu.age > age )
}
*/
return age => { return state.students.filter( stu => stu.age > age ); }
},
// 使用根模块的getters
fullName(state, getters, rootGetters) {
return getters.fullname2 + rootGetters.counter;
}
/*
将方法放入vue实例中的计算属性
import { mapGetters,mapAction } from 'vuex';
computed:{
...mapGetters(['powerCounter']);
...mapAction(['gansha']);
...mapGetters({
powerCounter: 'powerCounter',
more20stu: 'more20stu'
})
}
*/
},
// 划分模块
modules: {
// 取出: $store.state.a
a: {
state: {},
mutations: {}, // 与原来一样
getters: {
// 与原来一样
}
},
b: moduleB
}
})
export default store;
使用
{{$store.state.token}}
{{$store.getters.powerCounter}}
{{$store.getters.more20stu}}
{{$store.getters.moreAgeStu(20)}}
methods: {
addition() {
this.$store.commit('increase');
},
// @click="additionCounter(5)"
additionCounter( count ) {
this.$store.commit('increaseCounter', count)
// 这将传入一整个对象
this.$store.commit({
type: 'incrementCount',
count
})
// this.$store.dispatch('aUpdateInfo', 'canshu'); 异步操作时,使用actions调用mutation提交
}
}
文件抽取
事件总线
Vue.prototype.$bus = new Vue()
this.$bus.$emit()
this.$bus.$on("", () => { })
minxins
混入,可复用功能,引入后就可以直接用minxins的属性、方法
多个组件公用minxins,不会共享变量
created,mounted,会合并,先混和,再组件
十、前端基础
js
var、const、let
let存在缺陷
ES5之前: if与for没有块级作用域,共用循环i, for,使用闭包,函数存在作用域
ES5:闭包
for(var i = 0; i < btns.length; i++){
(function(i){
btn[i].addEventListener('click', function(){
console.log("i");
})
})(i);
/*函数中有自己的i,作用域
(function(){})(i);
*/
}
ES6 : let / const let存在自己的作用域,const常量
const btns = documents.getElementsByTagName('button');
//如果是var,只会是5
for(let i = 0; i < btns.length ; i++){
btns[i].addEventListener('click', function(){
console.log("点击"+ i);
})
}
const的对象不能修改,但是属性可以修改
对象增强写法
//1.属性增强方法
let name = "myname";
//直接赋值
let obj = {
name
}
//2.方法增强
let obj = {
run(){
console("ES6");
}
}
//未增强
let obj = {
run: function(){
}
}
两空格
箭头函数
const sum = (num1, num2) => {
return num1 + num2;
}
const power = num => {
return num * num;
}
const mul = (num1, num2) => num1 + num2;
箭头函数中的this 引用最近作用域的this
字符串
str.startsWith("")
.endWith("")
.includes("");
字符串模板
var str = `${age}你好`
解构
var test = ( {name} ) => console.log("hello", name)
test( {name: "sdf"} );
对象优化
Object.keys(person) // array[]
Object.values(person) // arrays[]
Object.entries(person) /* [
[ "name", "mingyue" ],
]*/
Object.assign(origin, newObj1) // 赋值到origin中
promise
promise是异步编程的一种解决方案,如多层ajax嵌套
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("data"); //将数据传入下一步
// 或者
reject("error message")
}, 1000)
}).then((data) => {
// 正式进行处理
// 1. 进行链式
return new Promise((resolve) => {
resolve("data1"); // 交给下一个处理
})
// 2. 简洁链式
return Promise.resolve("data2");
return Promise.reject()
throw "error";
// 3. 更简洁
return "data2";
}).catch((err) => {
// 错误处理
})
// then(success(), err())
async异步
sync同步
异步运行
// 两个异步运行,都获取结果才then
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result1');
}, 2000)
}),
new Promise((resolve, reject)) => {
setTimeout(() => {
resolve('result2');
}, 1000)
}
]).then(results => {
console.log(results);
})
实现
通过定义两个变量来保证都运行完成
数组filter
Array.filter(function(currentValue, indedx, arr), thisValue)
<script>
let arr = [1,2,3,4,5,6,8];
let newArr = arr.filter(function(arr){
return arr < 3;
});
console.log(newArr);
</script>
数组map
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
<script>
let arr = [1,2,3,4,5,6,8];
let newArr = arr.map(function(arr){
return arr * 2;
});
console.log(newArr);
</script>
数组reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
<script>
let arr = [1,2,3,4,5,6,8];
let sum = arr.reduce(function(preValue, value){
return preValue + value;
}, 0);// (函数, 初始值)
console.log(sum);
</script>
<script>
let total = nums.filter(n => n < 100).map(n => n * 2).reduce( (pre, n) => pre + n);
</script>
对象解构
var obj = {
name: "",
age: 12
};
var {age, name} = obj;
push合并数组
arr.push(…ar2);
find - some - every - foreach
every()是对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true。
some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。
arr.find(item => item.flag); //找到第一个
arr.foreach(item = > .......);
var arr = [ 1, 2, 3, 4, 5, 6 ];
console.log( arr.some( function( item, index, array ){
console.log( 'item=' + item + ',index='+index+',array='+array );
return item > 3;
}));
console.log( arr.every( function( item, index, array ){
console.log( 'item=' + item + ',index='+index+',array='+array );
return item > 3;
}));
css
stick
超过则用fixed,常做导航栏
类类名选择器
li > [class^="local-nav-icon"]
before
.user::before {
content: "";
display: block;
width: 23px;
height: 23px;
background: url(../img/sprite.png) no-repeat -59px -193px;
background-size: 104px auto;
margin: 0 auto;
margin-top: 5px;
}
精灵图
.local-nav li [class^="local-nav-icon"] {
margin-top: 5px;
width: 40px;
height: 40px;
background-color: red;
background: url(../img/sprite1.png) no-repeat 0px 0px;
/* bottom center 底部中心对齐 */
background-size: 40px auto;
}
.local-nav li .local-nav-icon-2 {
background-position: 0 -40px;
}
overflow:hidden
用于border-radius
overflow-y: scroll;
可以部分显示,可滑动
:nth-child(-n+2)
前两个
:nth-child(n+2)
第二个开始
渐变
background: -webkit-linear-gradient(left,#fa6b54, #fa974d);
箭头
.sales_head a{
position: absolute;
top: 4px;
right: 10px;
padding: 3px 20px 3px 6px;
border-radius: 18px;
background:-webkit-linear-gradient(left, #ff506a, #ff6bc4);
color: #fff;
}
.sales_head a:after{
content: '';
position: absolute;
top: 9px;
right: 9px;
width: 7px;
height: 7px;
border-top: 2px solid #fff;
border-right: 2px solid #fff;
transform: rotate(45deg);
}
省略号
不换行,省略号
.good-info p {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 3px;
}
vh
100 视口100
.content {
height: calc(100vh - 93px); /* 空格! */
overflow: hidden;
}
.content {
position: absolute;
top: 44px;
bottom: 49px;
}
居中显示
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%)
}
居中
left: 50%;
transform: translateX(-50%);
flex
开始
display: flex
父属性
- flex-direcction 设置主轴方向
- row 默认从左到右
- row -reverse 右到左
- column 上下
- column-reverse
- justify-content 主轴上子元素排列方式
- flex-start 头部开始
- flex-end 尾部开始
- center 在主轴居中对齐
- space-around 平分剩余空间
- space-between 两边贴边,剩下平分
- space-evenly 均等分
- flex-wrap 子元素是否换行,默认不换行,将会平分
- nowrap|wrap
- align-content 设置侧轴子元素的排列方式,多行
- flex-start 上到下
- flex-end 下到上
- center 居中
- space-around 平分剩余空间
- space-between 两边贴边,剩下平分
- stretch 拉伸,无高度,则铺满
- align-items 设置侧轴子元素的排列方式,单行
- flex-start 上到下
- flex-end 下到上
- center 居中
- stretch 拉伸,无高度,则铺满
- flex-flow 同时设置flex-direction,flex-wrap
- row | wrap
子属性
flex 占用的份数
- 20% 父级的20%
- 1
align-self
- flex-end
order 排列顺序,数值小靠前,默认0
固定布局
html,body{
width: 100%;
height:100%;
}
* {
padding: 0px;
margin: 0px;
}
.main {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.top, .bottom {
height: 20px;
}
.center {
flex: 1;
overflow-y: auto;
}
十一、商城项目
初始化
https://github.com/coderwhy/supermall
目录
配置
vue.config.js
module.exports = {
configureWebpack: {
resolve: {
alias: {
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'router': '@/assets'
}
}
}
}
.editorconfig
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
插件
npm install vue-router –save
先routes再router new VueRouter({routes})
注意路径问题
better-scroll
移动端下拉
https://github.com/ustbhuangyi/better-scroll/tree/v1.15.0
Iscroll
npm install better-scroll --save
npm install @better-scroll/core@next --save
import BScroll from 'better-scroll'
const scroll = new BScroll('.wrapper')
import BScroll from '@better-scroll/core'
wrapper需要有高度,其中只能有一个标签!
// 默认不实时检测,probeType: 2手指滑动过程检测,3滚动则检测
// click:true 按钮默认生效,div默认不生效
const bScroll = new BScroll(document.querySelector('wrapper'), {
probeType: 2, // 必须
click: true,
pullUpLoad: true // 拉到底部
})
bScroll.on('scroll', (position) => {
console.log(position);
})
bScroll.on('pullingUp', () => {
console.log('ok');
// 能继续拉
bScroll.finshPullUp()
})
scrollTo(0, 0, 4000)
:probe-type="3" 当做数字传入
出现bug,无法显示
原因: 图片没加载完,没高度,scrollheight未更新,better-scroll无法下拉
解决:
this.$refs.scroll.scroll.refresh()
刷新高度- 每加载完一张图片就更新 img.onload = function(){}
- @load发送到总线
Vue.prototype.$bus = new Vue()
this.$bus.$emit()
this.$bus.$on("", () => { })
防抖/节流
debounce/throttle
setTimeout没时间放到最后执行
// Home.vue
const refresh = this.debounce(this.$refs.scroll.refresh, 50);
this.$bus.$on("imgLoaded", () => {
refresh();
});
// 避免频繁调用
debounce(func, delay) {
let timer = null; // 相当于全局
return function(...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
}, delay)
}
}
吸顶灯
$el 获取组件的节点,offsetTop获取距离顶部高度
this.$refs.tabControll.$el.offsetTop;
切换原数据
keep-alive
数据完整
v-if="Object.keys(good).length ==0"
封装toast
index.js
import Toast from './Toast';
const obj = {};
obj.install = function(Vue) {
// 1. 创建组件构造器
const toastContrustor = Vue.extend(Toast);
// 2. new一个组件
const toast = new toastContrustor();
// 3. 将组件挂载上元素
toast.$mount(document.createElement('div'))
// 4. toast.$el对应的就是div
document.body.appendChild(toast.$el);
Vue.prototype.$toast = Toast;
}
Toast.vue
import Toast from './Toast';
const obj = {};
obj.install = function(Vue) {
console.log("jinru")
// 1. 创建组件构造器
const toastContrustor = Vue.extend(Toast);
// 2. new一个组件
const toast = new toastContrustor();
// 3. 将组件挂载上元素
toast.$mount(document.createElement('div'))
// 4. toast.$el对应的就是div
document.body.appendChild(toast.$el);
Vue.prototype.$toast = toast;
console.log("chuqu")
};
export default obj;
main.js
import toast from "../"
Vue.use(toast);
fastclick
解决移动端300ms延迟
npm install fastclick
import FastClick from 'fastclick';
FastClick.attach(document.body);
vue-lazyload
https://github.com/hilongjw/vue-lazyload
npm i vue-lazyload -S //--save
main.js
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
// or with options
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
// loading: require('./assets/img/common/placeholder.png')
attempt: 1
})
new Vue({
el: 'body',
components: {
App
}
})
template
<ul>
<li v-for="img in list">
<img v-lazy="img.src" >
</li>
</ul>
十二、饿了么项目
初始化
npm install -g @vue/cli
npm Install -g cnpm --registry=https://registry..npm.taobao.org
npm create eleme
npm run serve
reset.css
https://meyerweb.com/eric/tools/css/reset/
路由, 登录守卫
十三、工作总结
【app.xxx.js】存放的是项目中所有界面的业务逻辑代码,所以我们通常所说的异步组件就是指app.xxx.js文件,
index有时候只是引入js的html
app里放的只是公共apiurl,会有 文件0.xxx,1.xxx,不确定是哪个,打断点也走不到
本次查找在文件1找到的
【manifest.xxx.js】可以理解为webpack打包时生成的配置文件,无需过多的关注里面的代码;
【vendor.xxx.js】存放的是各个组件、界面公用的一些代码,即第三方的库或者公共的基础组件