css:https://www.w3school.com.cn/cssref/pr_dim_line-height.asp
一、邂逅Vuejs
1.Vue简介
- Vue是一个渐进式的框架:一点点重构;核心库以及生态系统;
- 有很多特点和web开发中常见的高级功能:可复用组件;
2.Vue.js安装
1.直接CDN引入:
开发环境:<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
生产环境:<script src="https://cdn.jsdelivr.net/npm/vue"></script>
2.下载和引用:
开发环境:https://vuejs.org/js/vue.js
生产环境:https://vuejs.org/js/vue.min.js
NPM安装:
3.Vuejs初体验
01-HelloVuejs.html:着重new app({})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app"><h2>{{message}}</h2></div>
<script src="../js.vue/js"></script>
<script>
//let(变量)/const(常量)
//编程范式:声明式编程;js:命令式编程
const app=new Vue({
el:'#app',//用于挂载要管理的元素
data:{//定义数据
message:'你好啊,李银河!'
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
movies:['星际穿越','大话西游','少年派','盗梦空间']
}
})
</script>
</body>
</html>
案例3:计数器:着重methods; v-on:click="(方法名)"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<h2>当前计数:{{counter}}</h2>
<button v-on:click="add">+</button>
<button v-on:click="sub">-</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
counter:0
},
methods:{
add:function(){
console.log('add被执行');
this.counter++
},
sub:function(){
console.log('sub被执行');
this.coumter--
}
}
})
</script>
</body>
</html>
4.Vue中的MVVM
计数器中的MVVM:
View是我们的DOM,在网页上展示出来的
Mode是我们抽离出来的obj
ViewMode是我们创建的Vue对象实例
ViewModel通过Data Bingging 让obj中的数据实时在DOM中显示
ViewModel通过DOM Listener来监听DOM事件,并通过methods中的操作,来改变OBJ中的数据。
5. Vue的生命周期
github->vue->tag版本
new Vue({el:'',data:,methods:{},beforeCreate:function(){},created:function(){},mounted:function(){}})
6.Vue的template
定义模板
二、插值语法
mustache语法: <h2>{{message+' '+firstname}}</h2>
v-once:<h2 v-once>{{message}}</h2> ,message不改变
v-html : <h2 v-html='link'></h2>,按照html格式进行解析
v-text: <h2 v-text="message"></h2> 中间接数据也会被覆盖
v-pre: <p v-pre>{{message}}</p> 跳过编译过程,直接显示{{message}}
v-cloak:防止Vue还没来得及解析,显示出不一样的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
<style>
[v-cloke]{
display:none;
}
</style>
</head>
<body>
<div id="app" v-cloke>{{message}}</div>
<script src="../js/vue.js"></script>
<script>
setTimeout(function(){
const app=new Vue({
el:'#app',
data:{
message:'你好啊'
}
})
},1000)
</script>
</body>
</html>
三、动态绑定属性v-bind
v-bind: <img v-bind:src="imgURL" alt=""><a v-bind:href="aHref">百度一下</a> :动态改标签
1.动态绑定class
通过对象
<h2:class="{'active':isActive}">Hello World</h2>
<h2:class="{'active':isActive,'line':isLine}">Hello World</h2>
<h2 class="tittle" :class="{'active':isActive,'line':isLine}">Hello World</h2>
<h2 class="tittle" :class="classes">Hello World</h2>
绑定class 通过数组
<h2 class="tittle" :class="[active,line]">{{message}}</h2>
v-bind语法糖:<img :src="imgURL" alt="">
<!DOCTYPE>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
//<h2 v-bind:class="{active:isActive,line:isLine}">{{message}}</h2>
<h2 class="tittle" v-bind:class="getClasses()">{{message}}</h2>
<button v-on:click="btnClick">按钮</button>
</div>
<script src="../vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
isActiove:true,
isLine:true
},
methods:{
btnClick:function(){
this.isActive=!this.isActive
},
getClasses:function(){
return{active:this.isActive,line:this.isLine}
}
}
})
</script>
</body>
</html>
2.动态绑定style
1:对象语法
<!DOCTYPE>
<html lang="en">
<head>
<meta charst="UTF-8">
<tittle>Tlttle</tittle>
</head>
<body>
<div id="app">
<h2 :style="{fontSize:finalSize + 'px',backgroundColor:finalColor}">{{message}}</h2>
<div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
finalSize:100,
finalColor:'red'
}
})
</script>
</body>
</html>
v-bind绑定style2:数组语法
<div v-bind:style="[baseStyles,overridingStyles]"></div>
四.计算属性
结合之后再显示
1.案例1
data:{computed:{fullName:function(){return this.firstName+' '+this.lastName}}}
<!DCOTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<h2>总价格:{{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
books:[
{id:110,name:'Unix编程艺术',price:119},
{id:111,name:'代码大全',price:105},
{id:112,name:'深入理解计算机原理',price:98},
{id:113,name:'现代操作系统',price:87},
]
},
computed:{
totalPrice:function(){
let result=0
for(let i=0;i<this.books.length;i++){
result += this.books[i].price
}
return result
}
})
</script>
</body>
</html>
2.setting和getting
<!DOCTYPE html>
<html>
<head>
<meta charst="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id='app'>{{fullName}}</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
firstName:'Kobe',
lastName:'Bryant'
},
computed:{
//计算属性一般只有set方法,只读属性,所以可以简写:fullName:function(){}
fullName:{
set:function(newValue){
const names=newValue.split('');
this.firstName=names[0];
this.lastName=names[1];
},
get:function(){
return this.firstName+' '+this.lastName
}
}
}
</script>
</body>
</html>
计算属性和methods对比:
计算属性:fullName;
methods: get FullName(sea(),get());
methods是每次都执行,而计算属性是会把数据缓存的,所以一般使用计算属性
3.块级作用域-let和var
es5之前if和for都没有会计作用域的概念,所以很多时候我们必须借助于function的作用域来解决应用外面变量的问题
es6中,加入了let
<!DOCTYPE html>
<html lang="en">
<head>
<meta charst="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
/*
var btns=document.getElementsByTagName('button');
for(var i=0;i<btns.length;i++){
btns.addEventListener('click',function(){
console.log('第'+num+'个按钮被点击');
})
})(i)
}
*/
const btns=document.getElementsByTagName('button')
for(let i=0;i<btns.length;i++){
btns[i].addEventListener('click',function(){
console.log('第'+i+'个按钮被点击');
})
}
</script>
</body>
</html>
4.const的使用和注意点
注意1:一旦给const修师的标识符被赋值之后,不能被修改
const name='why';
name='abc';
注意2:在使用const定义标识符,必须进行赋值
const name;
注意3:常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
const obj={
name:'why',
age: 18,
height: 1.88
}
obj.name='kobe';
5.对象增强写法
1.属性的简写:
//ES6之前
let name='why'
let age=18
let obj1={
name:name,
age: age
}
//ES6之后
let obj2={
name,age
}
2.方法的简写
//ES6之前
let obj1={
test:function(){
console.log('obj1的test函数');
}
}
//ES6之后
let obj2={
test(){
console.log('obj2的test函数')
}
}
obj2.test()
五.事件监听:v-on
<button v-on:click="increment">+</button>
语法糖:
<button @click="increment">-<button>
1.参数:
情况1:如果该方法不需要额外参数,那么方法后的()可以不添加。
如果方法中有一个参数,那么会默认将原生事件event参数传递过去:undefined
情况2:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
2.修饰符:
1、停止冒泡:
<button @click.stop="btnClick">按钮</button>
2、阻止默认行为
<input type="submit" value="提交" @click.prevent="submitClick">
<button @click.prevent="doThis"></button>
3、键盘修饰符
<input type="text" @keyup.enter="keyUp">
4、点击回调只会触发一次
<button @click.once="btn">按钮2</button>
六:条件判断
1.v-if,v-else-if,v-else
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<h2 v-if="isshow">
<div>abc</div>
{{message}}
</h2>
<h1 v-else>isShow为false时,显示我</h1>
</div>
<script src="../js/vue.js"></script>
<script>
const app=newVue({
el:'#app',
data:{
message:'你好啊',
isShow:true
}
})
</script>
</body>
</html>
v-if和v-show对比:
v-if当条件为false时,压根不会有对应的元素在DOM中
v-show当条件为false时,仅仅是将元素的display属性设置为none
当需要在显示与隐藏之间切换很频繁时,使用v-show
当只有一次切换时,通常使用v-if
一般使用v-if
2.登录切换小案例
加key 不复用,placeholder 添加文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<span v-if="isUser">
<label for="username">用户账号</label>
<input type="text" id="username" placeholder="用户账号" key="username">
</span>
<span v-else>
<label for ="email">用户邮箱</label>
<input type="text" id="email" placeholder="用户邮箱" key="email">
</span>
<button @click="isUser=!isUser">切换类型</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
issUser:true
}
})
</script>
</body>
</html>
七.v-for遍历数组和对象
1.遍历数组:
<li v-for="item in names">{{item}}</li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<ul>
<!--在遍历过程中,没有使用索引值(下标值)-->
<li v-for="item in names">{{item}}</li>
</ul>
<ul>
<!--//在遍历过程中,获取索引值-->
<li v-for="{item,index} in names">{{index+1}}.{{item}}</li>
</ul>
</div>
<script src="../js/vue.js></script>
<script>
const app=new Vue({
el:'#app',
data:{
name:['why','kobe','james','curry']
}
)}
</script>
</body>
</html>
2.遍历对象:
<li v-for="(value,key,index) in info">{{value}}-{{key}}-{{index}}</li>
1. 只获取value: <li v-for="item in info">{{item}}</li>
2. 获取key和value: <li v-for="(value,key) in info"{{value}}-{{key}}</li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<ul>
<li v-for="(value,key,index)">{{value}}-{{key}}-{{index}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
info:{
name:'why',
age:'18',
height:'1.88'
}
}
})
</script>
</body>
</html>
绑定key:
<li v-for="item inletters" :key="item">{{item}}</li>
如果不绑定,默认先插入,后面的元素再位移,效率很低
3.数组的响应式方法
可变长函数:
sun:functuin(...num){
console.log(num)l
}
push也是可变长函数
响应式方法:
1.push:在最后追加元素:this.letters.push('aaa','bbb','ccc)
2.pop: 删除数组中最后一个元素:this.letter.pop();
3.shift():删除数组中第一个元素: this.letter.shift()
4.unshift():在数组最钱买你添加元素:this.letters.unshift('aaa','bbb','ccc')
5.splice:删除、插入、替换元素
this.letters.splice(1,3,'m','n','1','x') 替换元素
this.letters.splice(1,0,'x','y','z') 插入元素
5.sort()排列:this.letter.sort()
6.reverse() 反转: this.letters.reverse()
注意:
通过索引值修改数组中的元素,不是响应式的
this.letters[0]='bbb'
可以使用splice:
this.letters.splice[0,1,'bbb']
可以用vue.set()
Vue.set(this.letters,0,'bbb')
4.电影点击案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in movies" :class="{active:currentIndex===index}">{{index}}.{{index}}
</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'app',
data:{
movies:['海王','海贼王','加勒比海盗','海尔兄弟'],
currentIndex:0
},
methods:{
liClick(index){
this.currentIndex=index
}
}
})
</script>
</body>
</html>
5.购物车案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<div v-if="books.length">
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in books">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<!--过滤器-->
<td>{{item.price|showPrice}}</td>
<td>
<button @clicck="decrement(index)" v-bind:disabled="item.count<=1">-</button>
{{item.count}}
<button @click="increment(index)">+</button>
<td><button @click="removeHandle(index)">移除</button></td>
<tr>
</tbody>
</table>
<h2>总价格:{{totalPrice|showPrice}}</h2>
</div>
<h2 v-else>购物车为空</h2>
</div>
<script src="../js/vue.js"></script>
<script src="main.js></script>
</body>
</html>
const app=new Vue({
el:'app',
data:{
books:[{price:'18,name:'数据库',data:'2020-11'},{price:'18,name:'数据库',data:'2020-11'},{price:'18,name:'数据库',data:'2020-11'},{price:'18,name:'数据库',data:'2020-11'}]
},
methods:{
increment(index){
this.books[index].count++
},
decrement(index){
this.books[index].count--
},
removeHandle(index){
this.books.splice(index,1)
}
},
computed:{
totalPrice(){
let totalPrice=0
for(let i=0;i<this.books.length;i++){
total+=this.books[i].price*this.books[i].count
}
return totalPrice
}
},
filters:{
showPrice(price){
return '¥'+price.toFixed(2)
}
}
})
total的高阶函数编程 filter/map/reduce
const nums=[10,20,111,222,444]
let total=nums.filter(function(n){
return n<100
}).map(function(n){
return n*2
}).reduce(function(prevValue,n){
return prevValue+n
},0)
console.log(total)
更简洁的写法:
let total=nums.filter(n=>n<100).map(n=>n*2).reduce((pre,n)=>pre+n);
八.表单绑定v-model
使用v-model指令实现表单元素和数据的双向绑定
<div id="app">
<input type="text" v-model="message">
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
let app=new Vue({
el:'#app',
data:{
message:''
}
})
</script>
可以将v-model用于textarea元素
<textarea v-model="message"></textarea>
<p>输入的内容是:{{message}}</P>
v-model实际是一个语法糖,他的本质是:
<input type="text" v-model="message">
<!--等同于-->
<input type="text" v-bind:value="message" v-on:input="message=$event.target.value">
v-model结合radio使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<label for="male">
<!--如果没有v-model要使用name才会只选一个,label:可以点文字-->
<input type="radio" id="male" value="男" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
</label>
<h2>您选择的性别是: {{sex}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
sex:'女'
}
})
</script>
</body>
</html>
单选框:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<h2>您的选择是:{{isAgree}}</h2>
<button :disabled="!isAgree">下一步</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
isAgree:'false'
}
})
</script>
</body>
</html>
多选框
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<input type="checkbox" value="篮球" v-model="hobbies">篮球
<input type="checkbox" value="足球" v-model="hobbies">足球
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
<h2>您的爱好是:{{hobbies}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
hobbies:[]
}
})
</script>
</body>
</html>
下拉框:select
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<!--选择1个-->
<select name="abc" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
<h2>您选择的水果是:{{fruit}}</h2>
<!--选择2个-->
<select name="abc" v-model="fruits"multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="榴莲">榴莲</option>
<option value="葡萄">葡萄</option>
</select>
</div>
<script src="../js/vue.js"></script>
<script>
const spp=newVue({
el:'#app',
data:{
fruit:'香蕉',
fruits:[]
}
})
</script>
</body>
</html>
值绑定
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<label v-for="item in originHobbies" :fro="item>
<input type="checkbox" :value="item" :id="item" v-model="nobbies">{{item}}
</label>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
originHobbies:['篮球','足球','乒乓球','台球','高尔夫球']
}
})
</script>
</body>
</html>
修饰符
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<!--1.修饰符:lazy: 失去焦点或者回车才会model变化-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>
<!--2.修饰符: number : 不会让number转换为string类型-->
<input type="number" v-model.number="age">
<h2>{{age}}--{{typeof age}}</h2>
<!--3.修饰符:trim: 去除两边的空格-->
<input type="text" v-model.trim.="name">
<h2> 您输入的名字:{{name}}</h2>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
age:0
name:''
}
})
</script>
</body>
</html>
九.组件化component
任何应用都会被抽象成一棵组件树
步骤:创建组件构造器、注册组件、使用组件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<!--3.使用组件-->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
<!--1.创建组件构造器对象-->
const cpnC=Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
<p>我是内容,呵呵呵</p>
</div>`
})
<!--2.注册组件-->
Vue.component('my-cpn',cpnC)
</script>
</body>
</html>
局部组件:
const app=new Vue({
el:'#app',
components:{
cpn:cpnc
}
})
父子组件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<div id="app">
<cpn1></cpn1>
</div>
<script src="../js/vue.js"></script>
<script>
<!--1.创建第一个组件构造器-->
const cpnC1=Vue.extend({
template:`
<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈</p>
</div>
`
})
<!--2.创建第二个组件构造器(父组件)-->
const cpnC2=Vue.extend({
template:`
<div>
<h2>我是标题2</h2>
<p>我是内容,呵呵呵</p>
<div>
`,
components:{
cpn1:cpnC1
}
)}
<!--root组件-->
const app=new Vue({
el:'#app',
components:{
cpn2:cpnC2
}
})
</script>
</body>
</html>
注册组件语法糖
<!--1.全局-->
Vue.component('cpn1',{
template:`
<div>
<h2>我是标题2</h2>
<p>我是内容,呵呵呵</p>
</div>
`
})
<!--2.局部-->
const app=new Vue({
el:'#app',
components:{
'cpn2':{
template:`
<div>
<h2>我是标题2</h2>
<p>我是内容,呵呵呵</p>
</div>
`
}
}
})
模板的分离写法
<!-- 使用script-->
<script type="text/x-template" id="myCpn">
<div>
<h2>组件标题</h2>
<p>我是组件的内容</p>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
let app=new Vue({
el:'#app',
components:{
'my-cpn':{
template:'#myCpn'
}
}
})
</script>
<!-- 使用template-->
<template id="myCpn">
<div>
<h2>组件标题</h2>
<p>我是组件的内容</p>
</div>
</template>
<script>
let app=new Vue({
el:'#app',
components:{
'my-cpn':{
template:'#myCpn'
}
}
})
</script>
在vue定义tittle,注册组件是用不到的
组件的data必须使用函数,让调用者都有自己独立的数据,不然每一次调用指向的都是同一个对象地址,会发生连锁反应
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn',
data(){
return{
counter:0
},
methods:{
increment(){
this.counter++
},
decrement(){
this.counter--
}
}
})
</script>
父子组件的通信
1.通过props向子组件传递数据
2.通过事件向父组件发送信息
props:{
1.类型限制
cmovies:Array
cmessage:String
2.提供一些默认值,以及必传值
cmessage:{
type:String,
default:'aaa',
required:true
},
3.类型是对象或者数组时,默认值必须是一个函数
cmovies:{
type:Array,
default(){
return[]
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<cpn v-bind:cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src='../js/vue.js'></script>
<script>
const cpn={
template:'#cpn',
props:{['cmovies','cmessage']},
data(){
return []
},
methods{}
}
const app=new Vue({
el:'#app',
data:{
movies:['haiwang','海贼王','海尔兄弟'],
message:'你好啊'
},
components:{
cpn
}
})
</script>
</body>
</html>
父子组件通信-props驼峰标识
props:{cInfo:{}_
<div id="app">
<cpn:c-info="info"></cpn>
子传父:自定义事件
1.在子组件中,通过$emit()来触发事件;
2.在父组件中,通过v-on来监听子组件事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UFT-8">
<tittle>Tittle</tittle>
</head>
<body>
<!--父组件模板-->
<div id="app">
<cpn @itemclick="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
<!--子组件-->
const cpn={
template:'#cpn',
data(){
return{
categories:[
{id:'aaa',name:'热门推荐'},
{id:'bbb',name:'手机数码'},
{id:'ccc',name:'家用家电'},
{id:'ddd',name:'电脑办公'}
]
}
},
methods:{
btnClick(item){
this.$emit('itemclick',item)
}
}
}
<!--父组件-->
const app=new Vue({
el:'#app',
commponents:{
cpn
},
methods:{
cpnClick(){
concole.log('cpnClick');
}
}
})
</script>
</body>
</html>
父子组件通信案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" :value="dnumber1" @input="number1Input">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:'#app',
data:{
num1:1,
num2:0
},
methods:{
num1change(value){
this.num1=parseFloat(value)
},
num2change(value){
this.num2=parseFloat(value)
}
},
components:{
cpn:{
template:'#cpn',
props:{
number1:Number,
number2:Number
},
data(){
return{
dnumber1:this.number1,
dnumber2:this.number2
}
methods:{
num1Input(event){
this.dnumber1=event.target.value;
<!--2.为了让父组件可以修改值,发出一个事件-->
this.$emit('num1change',this.dnumber1)
<!--3.同时修师dnumber2的值-->
this.dnumber2=this.dnumber1*100
this.$emit('num2change',this.dnumber2);
}
num2Input(event){
this.dnumber=event.target.value;
this.$emit('num2change',this.dnumber)
this.dnumber1=this.dnumber2/100;
this.$emit('num1change,this.dnumber1);
}
}
}
}
})
</script>
</body>
</html>
父子组件的访问方式:(父访问子)
$children
new Vue({
methods:{
btnClick(){
for(let c of this.$children){
c.showMessage();
}
this.$childre[3].name
ref
<cpn ref="aaa">
this.$refs.aaa.name
(子访问父)$parent
new Vue({
components:{
cpn:{
template:'#cpn',
components:{
ccpn:{
template:'#ccpn',
methods:{
btnClick(){
console.log(this.$parent);
console.log(this.$parent.name);
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
}
})
slot 插槽
<cpn><span>哈哈哈</span></cpn>
<template id="cpn>
<div>
<slot>我是擦超的默认值</slot>
</div>
</template>
1.插槽的基本使用<slot></slot>
2.插槽的默认值<slot>button</slot>
3.如果有多个值,同时放入到组件进行替换时,一起作为替换元素
编译作用域:
Stack Overflow/GitHub
在自己作用域查找变量
作用域插槽的使用
为了用一样的数据按不同形式呈现出来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<!--目的是获取子组件中的planguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data"><{{item}}</span>-->
<span>{{slot.data.join('-')}}</span>
</template>
</span>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join('#')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="planguages">
<ul>
<li v-for="item in planguages">{{item}}</li>
</ul>
</div>
</template>
<script src="../js/vue.js"></script>
const app=new Vue({
el:'#app',
components:{
cpn:{
template:'#cpn',
data(){
return{
planguages;['JavaScript','C++','Java','Python','Go']
}
}
}
}
})
</script>
</body>
</html>
模块化开发
防止命名冲突>闭包:同时可以使用别的模块的东西
var ModulA=(function(){
//1.定义一个对象
var obj={}
//2.在对象内部添加变量和方法
obj.flag=true
obj.myFunc=function(info){
concole.log(info);
}
//3.将对象返回
return obj
}){}
if(ModuleA.flag){
console.log('小明是个天才');
}
ModuleA.myFunc('小明长得帅')
console.log(ModuleA);
CommonJS
导入和导出
//导出
module.exports={
flag:true,
test(a,b){
return a+b
},
demo(a,b){
return a*b
}
}
//导入
let {test,demo,flag}=require('moduleA');
//等同于
let _mA=require('moduleA');
let test test=_mA.test;
let demo=_mA.demo;
let flag=_mA.flag;
ES模块化的导入导出
//导出:
//在需要导出数据的js文件写
//方式1
export{
flag,sum
}
//方式2:
export var num1=1000;
export var height=1.88
//导出函数、类
export function mul(num1,num2){
return num1*num2
}
exxport class Person{
run(){
console.log('在奔跑');
}
}
//5.export Default,只能有1个,导入时自主命名
export default function(argument){
console.log(argument);
}
//导入
//在需要导入数据的js文件写
//1.导入的{}中定义的变量
import {flag,sum} from "./aaa.js";
if{flag{
console.log('小明');
console.log(sum(20,30));
}
//2.直接导入export定义的变量
import {num1,height} from "./aaa.js";
console.log(num1);
console.log(height);
//3.导入export的function/class
import {mul,Person) from "../aaa.js";
console.log(mul(30,50));
const p=new Person();
p.run()
//4.导入默认
import addr from "./aaa.js";
addr('你好啊'));
//5.导入全部
import * as from ",/aaa.js";
//html文件
<script src="info.js" type="module"></script>
<script src="module.js" type="module"></script>
webpack
webpack安装
1.查看自己的node版本:node -v
2.全局安装webpack: npm install webpack@3.6.0 -g
3.局部安装webpack:npm install webpack@3.6.0 --save-dev
当在package.json定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
在终端直接执行webpack命令,使用的全局安装的webpack
webpack的使用过程
使用webpack指令打包: webpack src/main.js dist/bundle.js
npm run build->webpack
//1.使用commonjs的模块化规范
const {add,mul}=require('./mathUtils.js')
console.log(add(20,30));
//2.使用ES6的模块化的规范
import {name,age,height} from "./info";
console.loge(name);
//3.依赖css文件
require('./css/normal.css')
打包后会在dist文件下,生成一个bundle.js文件,是webpack处理了项目直接文件依赖生成的一个js文件,我们只需要将这个js文件在index.html中引入即可
<script src="./dist/bundle.js"></script>
webpack.config.js配置和package.js
可以将入口出口这讲个参数写在配置中,在运行时直接读取,这样就不用每次使用webpack的命令都需要协商入口和出口了:
const path=require('path')
module.exports={
entry:'./drc/main.js',
output:{
path:path.path.resolve(__dirname,'dist'),//注意:path通常是一个绝对路径
filename:'bundle.js'
}
}
webpack中使用css文件的配置
www.webpackjs.com
loader:可以不仅仅webpack打包js文件
1.通过npm安装需要使用的loader
npm install --save-dev css-loader
npm install --save-dev style-loader
2.在webpack.config.js中的modules关键字下进行配置
//webpack.config.js下
module.exports={
module:{
rules:[
{
test:/\.css$/,//正则表达式
//css-loder只是负责j加载,style-loader负责将样式添加到DOM中,从右向左
use:['style-loader','css-loader']
}
]
}
}
less文件的处理
依赖less文件,main.js
require('./css/special.less')
www.webpackjs.com,点击less-loder的安装
图片文件的处理
安装url-loader
涉及图片的,都加上:/dist/:配置: publicpath:'dist/'
大于配置limit,需要安装file-loader
文件命名:use: name:'img/[name].[hash:8].[ext]'
ES6转ES5的label
1.npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
2.配置webpack.config.js文件
{
test:/\.m?js$/,
exclude:/{node_modules|boewr_components}/,
use:{
loader:'babel-loader',
options:{
presets:['es2015']
}
}
}
3.重新打包。查看bundle.js文件,发现其中的内容变成了ES5的语法
使用VUE的配置过程
1.安装vue
npm install vue--save (安装运行都需要依赖vue,而不是仅仅开发,不接-dev)
2.在main.js中直接使用
import Vue from 'vue'
const app=new Vue({
el:'#app',
data:{
message:'Hello Webpack'
}
在在html挂在vue
3.两版本:runtime-only:不支持template ; runtime-compiler: 支持template
指定版本:
modules.eaports={
resolve:{
alias:{
'vue$':'vue/dist/vue.esm.js'
}
}
}
创建vue时template和el的关系
Vue使用的终极版本
npm install vue-loder vue-template-compiler --save-dev
vue-loader加载文件,vue-template-compiler编译文件
想只用vue-loder还需要配置,或者找低于13版本,去package.json修改
<template>
<div>
<h2>我是cpn组件的标题</h2>
<p>我是cpn组件的内容</p>
<h2>{{name}}</h2>
</Cpn>
</div>
</template>
<script>
import Cpn from './Cpn'
export default{
name:"Cpn",
components:{
Cpn
},
data(){
return{
name:'CPN组件的name'
}
}
</script>
如果想要省略后缀名,可以在配置文件webpack.config.js中添加:
module.exports={
resolve:{
extensions:['.js','.css','.vue']\ }
}
横幅plugin的使用
plugin是插件,对webpack现有功能的各种扩展,比如打包优化,文件压缩等
plugin是对webpack本身的扩展,是一个扩展器;
loader主要用于转换某些类型的模块,它是一个转换器
plaugin的使用过程:
1.通过npm安装需要使用的plugins
2.在webpack.config.js中的plugins中配置插件
添加版权的plugin: BannerPlugin
1.在webpack.config.js文件中添加:
const webpack=require('webpack')
module.exports={
plugins:[
new webpack.BannerPlugin('最终版权归aaa所有')
]
}
2.重新打包
HtmlWebpackPlugin的使用
HtmlWebpackPlugin作用:
1.自动生成一个index.html文件(可以指定模板来生成)
2.将打包的js文件,自动通过script标签插入到body中
安装插件:
1.npm install html-webpack-plugin --save-dev
2.在配置文件webpack.config.js中:
const HtmlWebpackPlugin=require('html-webpack-plugin')
module:export={
plugins:[
new webpack.BannerPlugin('最终版权归aaa所有'),
new HtmlWebpackPlugin({
template:'index.html'
})
]
}
3.npm run build
更换目录:cd 05-webpack
UglifyjsWebpackPlugin
UdlifyWebpackPlugin:丑化,对打包的js文件压缩
1.npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
2.修改webpack.config.js文件,使用插件
const UglifyjsWebpackPlugin=require('uglifyjs-webpack-plugin')
Plugins:[new UglifyjsWebpackPlugin()]}
3.npm run build
webpack-dev-server服务器搭建
避免多次打包,修改时可自动刷新,等完成之后在npm run build
1.npm install webpack-dev-seaver@2.9.3--save-dev
2.配置
modules=export{
devServer:{
contentBase:'.dist',
inline:ture //实时刷新
}
3. 配置是局部,要使用全局
方法1../node_modules/.bin/wenpack/webpack-dev-server //写相对路径,找到文件夹
方法2.去package.json文件,{"scripts:{"dev":"webpack-dev-server --open"}}//优先到本地找
4. npm run dev
配置文件的抽离
开发和发布依赖不同的配置文件
1.安装 npm install webpack-merge//对两个文件进行合并
2.base.config.js
module.exports={
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,'../dist')
}
prop.config.js编译文件
const UglifyWebpackPlugin=require('uglifyjs-webpack-plugin')
const webpackMerge=require('webpack -merge')
const baseConfig=require('./base.config')
module.export=webpackMerge(baseConfig,{
plugins:[
new UglifyjsWebpackPlugin()
]
})
dev.confug.js开发文件
3.package.json
{"scripts":{"build":"webpack --config ./build/prod.config.js","dev":"webpack-dev-server --open --config ./build/dev.config.js"}}
4.npm run build
5.npm run dev
Vue CLI脚手架的介绍和安装
CLI Command-Line Interface
使用vue-cli可以快速搭建Vue开发环境以及对应的webpack配置
Vue CLI使用前提-Node,webpack
1.安装NodeJS
网址:http://nodes.cn/download/
2.检查安装的版本
xmg$ node -v
xmg$ npm -v
npm: Node Package Manager
3.webpack的全局安装
npm install webpack -g
Vue CLI的使用
https://cli.vue.js.org/zh/guide/installation.html
1.安装vue脚手架
npm install -g @vue/cli
2.拉取2.x模板
npm install @vue/cli-init -g
3.VueCLI2初始化项目: vue init webpack my-project
Vue CLI3初始化项目:vue create my-project
CLI2初始化项目的过程
vue init webpack my-project
?propject name my-project
?project description test vue cli2
?Author coderwhy
>Runtimer+Compiler Runtime-only
?install vue-rounter 安装路由
?use eslint to lint your code 使编码规范
?pick an eclint preset Standard
?Set up unit tests 单元测试 n
?Setup e2e tets tests with Nightwatch 端到端自动测试 n
Yes,use NPM
CLI2的目录结构解析
v8引擎,直接把js编译成二进制代码,跳过字节码
安装CLI错误和ESlint规范
错误:去到目录删除ache
eslint: 编写代码规范
关掉eslint :config->index.js->useesline:false
runtime-compiler和runtime-only
template->ast->render->vdom->真实DOM
区别只在main.js
const cpn={
template:'<div>{{message}}</div>',
data(){
return{
message:'我是组件message’
}
}
}
new Vue({
el:'#app',
render:function(createElement){
//1.普通用法:createElement('标签',{标签的属性},['])
return createElement('h2',
{class:'box'},
['Hello World', createElement('button',['按钮'])])
//2.传入组件对象
return createElement(cpn)
}
})
//由vue-template-compiler解析成render函数(开发时依赖)
VueCLI3创建项目和目录结构
rc-run command
Vue CLI3 配置文件修改,查看
运行时编译runtime-compiler
配置隐藏
vue ui 图形化界面操作
git 本地仓库
创建vue.config.js修改配置
箭头函数的基本使用和this指向
<script>
//1.放入两个参数
const sum=(num1,num2)=>
return num1+num2
}
//2.放入一个参数
const power=num=>{
return num*num
}
//3.函数中打印,如果按4写,打印undefined,console.log返回值
const test=()=>{
console.log('Hello');
}
//4.函数只有一行
const mul=(num1,num2)=>num1*num2
//箭头函数中的this是向外层作用域一层层查找
const obj={
aaa(){
setTimeout(funxtion(){
setTimeout(function(){
console.log(this);//window
setTimeout(()=>{
console.log(this);//window对象
})
})
setTimeout(function(){
console.log(this);//window
setTimeout(()=>{
console.log(this);//obj对象
})
}
}
什么是路由和其中的映射关系
映射表:【内网ip1:电脑mac标识1】
前端渲染后端渲染和前端路由后端路由
url的hash和HTML5的history
改变url没刷新
1.URL的hash也就是锚点,本质是该百年windoe.location的href属性,改变url页面没刷新
hash :location.hasn='aaa' -> localhost:8081/#/aaa
2.栈结构
history.pushState({},'','home')
history.back() :移除最新一个,显示倒数第二个,出栈
history.replaceState({},'','home') :替换,没有返回
history.pushState({},'','home') -> history.upshState({},'','abort') ->history.go(-1)跳到倒数第二个 、history.forward(-1)
vue-router 安装和配置
1..安装: npm install vue-router --save
2.在模块化工程中使用(通过Vue.use())
导入路由对象,并且调用Vue.use(VueRouter)
创建路由实例,并且传入路由映射配置
在Vue实例中挂在创建的路由实例
//index.js
//配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
//1.通过Vue.use(插件),安装插件
Vue.sue(VueRouter)
//2.创建VuerRouter对象
const routes=[]
const router=new VueRouter({
routes
})
//3.将router对象传入到Vue实例
export default router
//main.js
import Vue from 'vue'
import App from './App'
import couter from'./router'
Vue.config.productionTip=false
new Vue({
el:'#app',
router,
render:h=>h(App)
})
路由映射配置和呈现
3.使用
创建路由组件
配置路由映射:组件和路径映射关系
使用路由:通过<router-link>和<router-view>
//index.js
//配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from'../components/Home'
import About from '../comopnents/About'
//1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)
//2.创建VuerRouter对象
const routes=[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
const router=new VueRouter({
routes
})
//3.将router对象传入到Vue实例
export default router
// App.vue
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default{
name:'App'
}
</script>
<style>
</style>
路由默认值和修改为history模式
const routes=[
{
//默认值
path:'/',
redirect:'/home'
},
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
//改为history
const router=new VueRouter({
//配置路由和组件之间的应用关系
routes,
mode:'history'
})
router-link其他属性
<router-link to="/home" tag="button" replace active-class="active">首页</router-link>
const router=new VueRouter({
//配置路由和组件之间的应用关系
routes,
mode:'history',
linkactiveClass:'active'
})
//或者在路由修改属性名,再修改样式
通过代码跳转路由
//App.vue
<template>
<div id="app">
<button @click="homeClick">首页</button>
<button @click="aboutClick">关于</button>
</div>
</template>
<script>
export default{
name:'App'
method:{
homeClick(){
//this.$router.replace('/home')
this.$router.push('/home')
}
aboutClick(){
//this.$router.replace('/about')
this.$router.push('/about')
}
}
}
</script>
vue-router 动态路由的使用
//app.vue
<router-link v-bind:to="'/user/'+useId">用户</router-link>
<script>
export default{
name:'App',
data(){
return{
useId:"list'
}
}
}
//User.vue
<template>
<div>
<h2>我是用户界面</h2>
<h2>{{userId}}</h2>
<h2>{{$.route.params.abc}}</h2>
</div>
</template>
<script>
export default{
name:"User",
computed:{
userId(){
//活跃的路由
this.$route.params.abc
}
//index.js
const routes=[
{
path:'/user/:abc',
component:User
]
打包文件的解析
路由懒加载的使用
把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件
路由懒加载主要的作用是将路由对应的组件打包成一个个的js代码块,只有在这个路由被访问到的时候,才加载对应的组件
//index.js
const Home=()=>import('../components/Home')
const Abour=()=>import('../components/About')
const User=()=>import('../components/User')
Vue.use(VueRouter)
const routes=[{
path:'',
redirect:'/home'
},
{
path:'/home',
component:Home
}]
//方式1:(不常用) 结合Vue的异步组件和Webpack的代码分析
const Home=resolve=>{require.ensure(['../components/Home.vue'],()=>{resolve(require('../components/Home.vue'))})};
//方式二:AMD写法
const About=resolve=>require(['../components/About.vue'],resolve);
//方式三:在ES6中,我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割
const Home=()=>import('../components/Home.vue')
嵌套路由
一个路径映射一个组件,访问者两个路径也会分别渲染这两个组件
实现嵌套路由有两个步骤:
1.创建对应的子组件,并且在路由映射中配置对应的子路由
2.在组件内部使用<router-view>标签
//index.js
const HomeNews=()=>import('../components/HomeNews')
const HomeMessage=()=>import('../components/HomeMessage')
const routes=[
{
path:'/home',
component:Home,
children:[{
path:'news'
component:HomeNews
},
{
path:'message',
component:HomeMessage
}
{
path:'',
component:HomeMessage
}
]
}
]
//Home.vue
<template>
<div>
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
</div>
</template>
vue-router参数传递
方式:params和query
URL: 协议://主机:端口/路径?查询
scheme://host:port/path?query#fragment
<router-link :to="{path:'/profile',query:{name:'why',age:18,height:1.88}}">
<template>
<h2>{{$route.query}}</h2>
</template>
//通过按钮点击,跳转
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
export default{
methods:{
userClick(){
this.$router.push('/user/'+this.userId"
},
profileClick(){
path:'/profile',
query:{
name:'kobe',
age:19,
height:1.87
}
}
}
}
vue-router和route的由来
router: new router
route:活跃的路由
vue router全局导航守卫
生命周期:创建created(){},挂载mounted(){},更新updated(){}
更换标题
export default{
name:"Home",
data(){
created(){
document.tittle='首页'
}
}
}
全局
//index.js
const routes=[{path:'/home',component:Home,meta:{tittle:'首页'}},{{path:'/user',component:User,meta:{tittle:'用户'}}]
const router=new VueRouter({
//配置路由和组件之间的应用关系
routes,
mode:'history',
linkActiveClass:'active'
})
router.beforeEach(to,from,next)=>{
//从from跳转到to
document.tittle=to.meta.tittle
//有路由嵌套的时候
document.tittle=to.matched[0].meta.tittle
next()
})
//前置守卫
router.beforeEach(to,from,next)=>{
//从from跳转到to
document.tittle=to.meta.tittle
//有路由嵌套的时候
document.tittle=to.matched[0].meta.tittle
next()
})
//后置钩子(hook)
router.afterEach(to,from)=>{
console.log('---')
})
vue-router-keep-alive
被缓存,重新点击时不会重新创建除了Profile和User
<keep-alive exclude="Profile,User">
<router-view/>
</keep-alive>
//记录上一次离开的path,嵌套路由
activated(){
this.$router.push(this.path);
beforeRouteLeave(to,from,next){
this.path=rhis.$route.path;
next()
}
tabBar-基本结构的搭建
1.vue init webpack tabbar
案例
//App.vue
<template>
<div id="app">
<router-view></router-view>
<main-tab-bar/>
</div>
</template>
<script>
import MainTabBar from './components/MainTabBar'
export default{
name:'App',
components:{
MainTabBar
}
}
</script>
<style>
@import ".asset/css/base.css";
</style>
//src/assets/css/base.css
body{
padding:0;
marginL0;
}
//src/assets/main.js
import Vue from 'vue'
import App from ',/App'
Vue.config.produtionTip=false
new Vue({
el:'#app',
render:h=>h(App)
})
//在components新增文件夹tabbar,TabBar.vue
<template>
<div id="APP">
<slot></slot>
</div>
</template>
<script>
export default{
name:"TabBar"
}
</script>
<style scope>
#tab-bar{
display:flex;
background:#f6f6f6;
position:fixed;
left:0;
right:0;
bottom:0;
box-shadow:0px -1px 1px rbga(100,100,100,.01);
}
.tab-bar-item{
flex:1;
text-align:center;
height:49px;
}
.tab-bar-item img{
width:24px;
height:24px;
}
</style>
//conponent/tabbar/TabBarItem.vue
<template>
<div class="tab-bar-item" @click="itemClick>
<div v-if="!isActive"><slot name="item-icon"></slot><.div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="activeStyle"><slot name="item-icon-active"></slot></div>
</div>
</template>
<script>
export default{
name:"TabBarItem",
props:{
path:String,
activeColor:{
type:String,
default:'red'
}
},
computed:{
isActive(){
return this.$route.path.indexOf(this.path)!=-1
},
activeStyle(){
return this.isActive ? {color:this.activeColor}:{}
}
}
methods:{
itemClick(){
this.$router.push(this.path)
}
</script>
<style scoped>
.tab-bar-item{
flex:1;
text-align:center;
height:49px;
font-size:14px;
}
.tab-bar-item img{
width:24px;
height:24px;
margin-top:3px;
vertical-align:middle;
margin-bottom:2px;
}
</style>
//cd 03-tabbar
//npm install vue-router --save运行时依赖
//src/routerindex.js
import vue from vue
import VueRouter from 'vue-router'
const Home=()=>import('../views/home/Home')
const Home=()=>import('../views/category/Category')
const Home=()=>import('../views/cart/Cart')
const Home=()=>import('../views/profile/Profile')
//1.安装插件
Vue.use(VueRouter)
//2.创建路由
const router=[
{
path:'',
redirect:'/home'
},
{
path:'/home',
component:'/Home'
},
{
path:'/category',
component:'/Category'
},
{
path:'/cart',
component:'/Cart'
},
{
path:'/profile',
component:'Profile'
}
]
const router=new VueRouter({
router
mode:'history'
})
//3.导出router
export default router
//src/vies/home/Home.vue|Category.vue|Profile.vue|cart.vue
<template>
<h2>分类</h2>
</template>
<script>
export default{
name:"Category"
}
</script>
<styly scoped>
</style>
//src/components/tabbar/MainTabBar.vue
//注意路径
<template>
<tab-bar>
<tab-bar-item path="/home" activeColor="pink">
<img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="">
<div slot="item-text">首页</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="pink">
<img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="">
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/cart" activeColor="pink">
<img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/shopcart_active.svg" alt="">
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="pink">
<img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="">
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
</template>
<script>
import TabBar from './components/tabbar/TabBar
import TabBarItem from './components/tabber/TabBarItem'
export default{
name:"MainTabBar",
components:[
TabBar,
TabBarItem
}
</script>
<style scoped>
</style>
//起别名
//webpack.base.conf.js
resolve:[
alias:{
'@':resolve('src'),
'assets':resolve(src/assets')
}
},
//MainTabBar.vue
<img slot="item-icon" src="~assets/img/tabbar/home.svg" alt="">
import TabBar from '@/components/tabbar/TabBar'
Promise
promise的介绍和基本使用
promise是异步编程的解决方案。
sync同步async异步
//链式编程
<script>
new Promise((resolve,reject)=>{
//第一次请求
setTimeout(()=>{
resolve()
},1000)
}).then(()=>{
//第一次处理
console.log('Hello World');
//第二次请求
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve()
},1000)
})
}).then(()=>{
//第二次处理
concole.log('Hello Vuejs');
return new Promise((resolve,reject)=>{
//第三次请求
setTimeout(()=>{
resolve()
},1000)
})
})
}).then(()=>{
//第三次处理
console.log('Hello Python');
})
propmise的三种状态
pending等待,fulfill满足(调用resolve时->then()),reject拒绝(调用reject时->catch())
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<script>
new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('error message')},1000)
}).then(data=>{
console.log(data);
},err=>{
console.log(err)
})
</script>
</body>
</html>
promise链式调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<script>
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('aaa')
},1000)
}).then(res=>{
//自己处理10行代码
console.log(res,'第一层的10行处理代码');
//对结果进行第一次处理
//简写:return Promise.resolve(res+'111')
//更简写: return res+'111'
//reject: return Promise.reject(;wrror message') 或者 throw 'error message'
return new Promise((resolve)=>{
resolve(res+'111')
})
}).then(res=>{
console.log(res,'第二层的10行处理代码');
return new Promise(resolve=>{
resolve(res+'222')
})
}).then(res=>{
console.log(res,'第三层的10行处理代码');
})
//如果是reject 后面还需要接:.catch(err=>{console.log(err);})
</script>
</body>
promise的all方法
拿到两个才执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle<tittle>
</head>
<body>
<script>
Promise.all([
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('result1')
},2000)
}),
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('result2')
},1000)
})
]).then(results=>{
console.log(results);
})
</script>
</body>
</html>
Vuex
vuex概念和作用解析
Vuex是一个专门为Vue.js应用程序开发的状态管理模式,就是把需要多个组件共享的变量全部存储在一个对象里面。响应式
token,头像,位置,收藏,购物车
Talk is cheap,Show me the code.
单页面到多页面状态管理切换
单页面:state,action,view
插件:devtools
我们来对使用步骤,做一个简单的小节:
01.提取出一个公共的store对象,用于保存在多个组件中共享
的状态
02.将store对象放置在new Vue对象中,这样可以保证在所有
的组件中都可以使用到
03.在其他组件中使用store对象中保存的状态即可
➢通过this.$store.state.属性的方式来访问状态
➢通过this.sstore.commit('mutation中方法)来修改状态
注意事项:
我们通过提交mutation的方式,而非直接改变
store.state.count.
这是因为Vuex可以更明确的追踪状态的变化,所以不要直接
改变store.state.count的值。
//index.js
import Vue from 'vue'
import Vuex from 'vues'
//1.安装插件
Vue.use(Vuex)
//2.创建对象
const moduleA={
state:{
name:'zhangsan'
",
mutation:{
updateName(state,payload){
state.name=payload
}
},
getters:{
fullname(state){
return state.name+'111'
},
fullname2(state,getters){
return getters.fullname+'222'
},
fullname3(state,getters,rootState){
return getters.fullname2+rootState.counter
}
}
action:{
aUpdateName(context){
setTimeout(()=>{
context.commit('updateName','wantwu')
},1000)
}
}
}
const store =new Vuex.Store({
state:{
counter:1000
student:[
{name:'why',age:18},
{name:'kobe',age:21},
{name:'james',age:15},
{name:'curry',age:39}
]
},
//参数修改只能在mutations中
mutations:{
//方法
increment(state){
state.count++
},
decrement(state){
state.count--
},
incrementCount(state,count){
state.counter+=count
},
//风格1
// addStudent(state,stu){
// state.student.push(stu)
}
//风格2
addStudent(state,payload){
state.student.push(payload.stu)
},
updateInfo(){
this.$store.commit('updateInfo')
},
getters:{
powerCouter(state){
return state.counter*state.counter
},
more20stu(state){
return state.students.filter(s=>s.age<20)
},
more20stuLength(state,getters){
return getters.more20stu.length
},
moreAgestu(state){
return function(age){
return state.students.filter(s=>s.age>age)
}
}
},
action:{
aUpdateInfo(context,payload){
return new Promise(resolve,reject)=>
setTimeout(()=>{
context.commit('uodateInfo');
console.log(payload);
resolve('111')
},1000)
})
}
},
modules:{
a:moduleA
}
})
//3.导出store独享
export default store
//main.js
import Vue from 'vue'
import App from'./App'
import store from './store'
Vue.config.productionTip=false
new Vue({
el:'#app',
store,
render:h=>(App)
})
//App.vue
<template>
<div id="app">
<h2>{{$store.state.counter}}</h2>
<h2>{{counter}}</h2>
<button @click="addition">+</button>
<button @click="$substration">-</button>
<button @click="addCount(5)"></button>
<button @click="assStudent"></button>
<button @click=" updateInfo"></button>
<button @click=" updateName"></button>
<button @click="asyncUpdateName">异步修改名字</button>
<h2>---App内容:getters相关信息---</h2>
<h2>{{$store.getters.powerCounter}}</H2>
<h2>{{$store.getter.more20stu}}</h2>
<h2>{{$store.getters.more20stuLength}}<h2>
<h2>{{$store.getters.moreAgeStu(12)}}</h2>
<h2>{{$store.getters.fullname}}</h2>
<h2>{{$store.getters.fullname2}}</h2>
<h2>{{$store.getters.fullname3}}</h2>
<hello-vueex/>
</div>
</template>
<script>
import HelloVuex from './components/HelloVuex'
export default{
name:'App',
components:{
HelloVuex
},
data(){
return{
message:'我是App组件'
}
},
methods:{
addition(){this.$store.commit('increment'},
subtraction(){this.$store.commit('decrement')}
addCount(count){
//payload:负载
//1.普通的提交封装
// this.$store.commit('incrementCount',count)
//2.另一种风格:
this.$store.commit({
type:'incrementCount',
count
})
},
addStudent(){
const stu={id:114,name:'alan',age:35}
this.$store.commit('addStudent',stu)
},
updateInfo(state){
state.info.name='coderwhy'
Vue.set(state.info,'address','洛杉矶')
Vue.delete(state.info,'age')
},
updateInfo(){
this.$store.dispatch('aUpadteInfo','我是携带的信息')
.then(res=>{
console.log('里面完成了提交');
console.log(res);
})
},
updateName(){
this.$.store.commit('updateName','lisi')
},
asyncUpdateName(){
this.$store.dispatch('aUpdateName')
}
}
}
</script>
//HelloVuex.vue
<template>
<div>
<h2>{{$store.state.counter}}</h2>
</div>
</template>
<script>
export default{
name:"HelloVuex"
}
</script>
<style scoped>
</style>
state单一状态树的概念
Single Source of Truth
统一放在store里面
响应规则
命名
网络模块的封装
安装axios :nom install axios --save
//main.js
import Vue from 'vue'
import App from './App'
//main.js
import axios from 'axios'
Vue.config.productionTip=false
new Vue({
el:'#app',
render:h=>h)App)
})
//1.
axios({
// url:'httpbin.org'
url:'http://123.207.32.32:8000/home/data',
params:{
type:'pop',
page:'1'
}
}).then(res->{
console.log(res);
})
//2.axios发送请求
axios.all([axioc({
url:'http://123.207.32.32:8000/home/multidata'
}),axios({
url:'http://123.207.32.32:8000/home/data',
params:{
type:'sell',
page:5
}
})]).then(result=>{ //then(axios.spread((res1,res2)=>
console.log(results);
console.log(results[0];
console.log(result[1];
})
//3.全局
//axios.default.baseURL=''
//axios.default.timeout=5000
//4.创建对应的axios实例
const instance1=axios.create({
baseURL:'http://123.207.32.32:8000',
timeout:5000
})
const instance2=axios.create({
baseURL:'http://222,111,33,33:8000',
timeout:1000,
header:{}
})
instance({
url:'/home/muitidata'
}).then(res=>{
console.log(res);
})
instance2({
url:'/home/data',
params:{
type:'pop',
page:1
}
}).then(res=>{
console.log(res);
})
//5.封装
request({
url:'/home/multidata'
}).then(res=>{
console.log(res);
}).catch(err->{
console.log(err);
})
封装
networks->requires.js
//request.js
import axios from 'axios'
export function request(config){
//1.创建axios 的实例
const insstance = axios.create({
baseURL:'http://123.207.32.32:8000',
timeout:5000
})
//2.axios的拦截器
instance.intercaptors.request.use(consig=>{
console.log(config);
//1.比如config中的一些信息不符合服务器的要求
//2.比如每次发送网络请求时,都希望在界面中显示一个请求的图标
//3.某些网路请求(比如登录(token)),必须携带一些特殊信息
return config
},err=>{
console.log(err);
})
//2.2响应拦截
instance.intercaptors.response.use(res=>{
return res.data
},err=>{
console(err);
})
//3.发送真正的网络请求
return instance(config)
}
项目创建和GITHUB托管
1.vue create supermall
2.npm run serve
3.托管
4.git clone https://github.com//coderwhy/supermall.git
5.cd supermall -> git status ->git add. ->git commit -m'初始化项目‘ ->git push
(或者 cd.. ->cd suoermall ->git remote add origin https://github.com.coderwhy/test.git ->git push -u origin master)
1.划分目录结构
2.css引用
nomalize.css
base.css @import ".nomalize.js
App.vue <style>@import "./assets/css/base.css";</style>
3.vue.config和editconfig
新建vue.config.js
module.exports={
configureWebpack:{
resolve:{
alias:{
'assets':'@/assets',
'common':'@/common',
'components':'@/components',
'network':'@network',
'views':'@/viess',
}
}
}
}
editorconfig
root=true
[*]
charset=utf-8
indent_style=space
indent_size=2
end_of_line=1f
insert_final_newline=true
trim_trailing_whitespace=true
//编写规范
4.tabbar引入和项目模块划分
0846
npm install vue-router --save
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home=()=>import('../views/home/Home')
//1.安装插件
Vue.use(VueRouter)
//2.创建router
const routes=[
//映射关系
{path:'',redirext:'/home'}{}{}
]
const router=new VueRouter({
routers,
mode:'history'
})
export default router
//views/main.js
import Vue from 'vue'
import App from'./App.vue'
import router from './router'
Vue.config.productionTip=false
//默认$bus 没有值
Vue.proptotype.$bus=new Vue()
new Vue({
render:h=>h(App),
router
}).$mount('#app')
5.小图标的修改和路径问题
小图标:public/favison.ico
//index.html
<html lang="en">
<head>
<link rel="icon" href="<%=BASE_URL %>favicon.ico">
//base url 是动态获取路径
6.首页导航栏的封装和使用
//src/views/home/Home.vue
<template>
<div id="home">
<nav-bar class="home-nav"><div slot="center">购物街</nav-bar>
<tab-control :tittles="['流行,'精选']" @tabClick="tabClick" ref="tabControl1" class="tab-control" v-show="isTabFixedd"/>
<scroll class='content' ref="scroll" :probe-type="3" @scroll="contentScoll" :pull-up-load="true" @pullingUp="load-more">
<home-swiper:banners="banners"/>
<recommend-view :recommends="recommend"/>
<feature-view/>
<tab-control :tittles="['流行,'精选']" @tabClick="tabClick" ref="tabControl2"/>
<good-list :goods="showGoods()"/>
</scroll>
<back-top @click.native="backClick" v-show="isShowBackTop"/ >
</div>
</template>
<script>
import HomeSwiper from './childComps/HomeSwiper'
impoer RecommendView from './childComps/Recommendview'
import FeatureView from './childCopms/FeatureView'
import NavBar from 'components/common/navbar/NavBar'
import TabControl from 'components/content/tabControl/TabControl'
import GoodList from './childComps/FeatureView'
import BackTop from 'components/content/backTop/BackTop'
import {getHomeMultidata}from "newwork.home"
export default{
name:"Home",
components:{
NarBar,
HomeSwiper,
RecommendView,
FeatureView,
TabControl,
BackTop
},
data(){
return{
banners:[],
recommends:[],
goods:{
'pop':{page:0,list:[]},
'new':{page:0,list:[]},
'sell':{page:0,list:[]},
},
currentType:'pop',
isShowBackTop:true
},
computed:{
showGoods(){
return this.goods[currentType].list
}
}
//网页加载前
created(){
//1.请求多个数据
this.getHomeMultidata()
//2.请求商品数据
this.getHomeGoods('pop')
this.getHomeGoods('new')
this.getHomeGoods('sell')
},
//网页加载后
mounted(){
const refresh=this.debounce(this.$refs.scroll.refresh,500)
//3.监听发item中图片加载完成
this.$bus.$on('itemImageLoad',()=>{
refresh()
})
},
methods:{
//监听方法 settimeout 会延迟到事件尾部执行,下一次循环执行
debounce(func,delay){
let timer=null
return function(...args){
if(timer) clearTimeout(()=>{
timer=setTimeout(()=>{
func.apply(this,args)
},delay)
}
},
tabClick(index){
switch(index){
case 0:
this.currentType='pop'
break
case 1:
this.currentType='new'
break
case 2:
this.currentType='sell'
break
}
this.$refs.tabControl1.currentIndex=index;
this.$refs.tabControl2.currentIndex=index;
},
backClick(){
this.$refs.scroll.scrollTo(0,0)
},
contentScroll(position){
//判断BackTop是否显示
this.isShowBackTop=(-position.y)>1000
//决定tabControl是否吸顶(position:fixed)
this.isTabFixed=(-position.y)>this.tabOffsetTop
},
loadMore(){
this.gethomeGoods(this.currentType)
//重新计算
this.$refs.scroll.scroll.refresh()
},
SwiperImageLoad(){
this.tabOffsetTop=this.$refs.tabControl2.$el.offsetTop;
//网络请求方法
getHomeMultidata(){
getHomeMultidata().then(res->{
this.banners=res.data.banner.list;
this.recommends=res.data.recommend.list;
})
},
getHomeGoods(type){
const page=this.goods[type].page+1
getHomeGoods(type,page).then(res=>{
this.goods[type].list.push(...res.data.list)
this.goods[type].page+=1
//完成上拉加载更多
this.$refs.scroll.finishPullUp()
})
}
}
</script>
<style scope>
#home{
height:100vh;
position:relative;
}
.home-nav{
background-color:var(--color-tint);
coloe:#fff;
position:fixed;
left:0;
right:0;
top:0;
z-index:9;
}
.tab-control{
position:sticky;
top:44px;
}
.content{
overflow:hiddem;
position:absolute;
top:44px;
bottom:49px;
left:0
right:0
}
}
</style>
//src/component/common/tabbar/TabBar.vue
<template>
<div id="nav-bar">
<div class="left"><slot name="right"></slot></div>
<div class="center"><slot name="center"></slot></div>
<div class="right"><slot name="right"></slot></div>
</div>
</template>
<script>
export default{
name:"NavBar"
}
</script>
<style scoped>
.nav-bar{
display:flex;
height:44px;
line-height:44px;
text-align:center;
}
.left,.right{
eidth:60px;
}
.center{
flex:1;
}
</style>
//network/home.js
import {request} from ",/request";
export function getHomeMultidata(){
return request({
url:'/home/multidata'
})
}
export function getHomeGoods(type,page){
return request({
url:'/home/data',
params:{
type,
page
}
})
)
7.首页请求多个数据
network/request.js
axios
8.轮播图的展示
//components/common/swiper/SwiperItem.vue
<template>
<div id="hy-swiper">
<div class="swiper @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
<slot></slot>
</div>
<slot name="indicator">
</slot>
<div class="indicator">
<slot name="indicator" v-if="showIndicator && slideCount>1">
<div v-for="(item,index) in slideCount" class="indi-item" :class="{active:index===currentIndex-1}" :key="index"></div>
</slot>
</div>
</div>
</template>
<script>
export default{
name:"Swiper",
props:{
interval:{
type:Number,
default:3000
},
animDuration:{
type:Number,
Default:300
},
moveRatio:{
type:Number,
default:0.25
},
showIndicator:{
type:Boolean,
default:true
},
data:function(){
return{
slideCount:0,
totalwidth:0,
swiperStyle:{},
currentIndex:1,
scrolling:false
}
},
methods:{
startTimer:funxtion(){
this.playTimer=windoe.seetInterval(()=>{
this.currentIndex++;
this.scrollCountent(-this.currentIndex*this.totalWidth);
},this.interval)
},
stopTimer:function(){
window.clearInterval(this.playTimer);
},
...
//views/childComps/HomeSwiper.vue
<template>
<swiper>
<swiper-item v-for="item in banners">
<a :href="item.link">
<img :src="item.image" alt="" @load="imageLoad">
</a>
</swiper-item>
</swiper>
</template>
<script>
import {swiper,SwiperItem}from 'components/common/swiper'
export default{
name:"HomeSwiper",
props:{
default(){
return []
}
},
data(){
return{
isLoad:false
}
}
components:{
Swiper,
SwiperItem
},
methods:{
imageLoad(){
if(!this.isLoad){
this.$emit('swiperImageLoad')
this.isLoad=true
}
}
}
}
</script>
9.推荐信息的展示
//home/childComps/RecommendView.vue
<template>
<div class="recommend">
<div v-for="item in recommends">
<a :href="item.link">
<img "src="item.image" alt="">
<div>{{item.tittle}}</div>
</a>
</div>
</div>
</template>
<script>
export default{
name:"RecommendView",
props:{
recommends:{
type:Array,
default(){
return[]
}
}
}
</script>
<style scoped>
.recommend{
display:flex;
width:100%;
text-align:center;
font-size:12px;
padding:10px 0 20px;
border-bottom:8px solid #eee;
}
.recommend-item{
flex:1;
}
.recommend-item img{
width:65px;
height:65px;
margin-bottom:10px;
}
}
10.featureview的封装
//FeatureView.vue
<template>
<div calss="feature">
<a href="https://act.mogujie.com/zzlx67">
<img src="~assets/img/home/recommend_bg.jpg" alt="">
</a>
</div>
</template>
<script>
export default{
name:"FeatureView"
}
</script>
<style scoped>
.feature img{
width: 100%;
}
</style>
11.TabControl 封装
//TabControl.vue
<template>
<div class="tab-control">
<div v-for="(item,index> in tittles"
class="tab-control-item"
:class="{active:index === currentIndex}" @click="itemClick(index)">
<span{{item}}</span>
</div>
</div>
</template>
<script>
export default{
name:"TabControl",
props:{
tittles:{
type:Array,
default(){
return[]
}
}
},
data(){
return{
currentIndex:0
}
},
methods:{
itemClick(index){
this.currentIndex=index;
this.$emit('tabClick',index)
}
}
}
</script>
<style scoped>
.tab-control{
display:flex;
text-align:center;
font-size:15px;
height:40px;
line-height:40px;
}
.tab-control-item{
flex:1;
}
.tab-control-item span{
padding:5px;
}
.active{
color:var(--color-high-text);
}
.active span{
border-bottom:3px solid var(--color-tint);
}
</style>
12.保存商品的数据结构设计
13.首页商品数据的展示
//conmmon/GoodList.vue
<template>
<div class="goods">
<goods-list-item v-for="item in goods" :goods-item="item"/>
</div>
</template>
<script>
import GoodsListItem from './GoodsListItem'
export default{
name:"GoodsList",
components:{
GoodsListItem
},
props:{
goods:{
type:Array,
default(){
return[]
}
}
}
}
</script>
//GoodsListItem.vue
<template>
<div class="goods-item">
<img :src="goodsItem.show.img" alt="" @load="imageLoad">
<div class="goods-info">
<p>{{class="goods-info"></P>
<span class="price">{{goodsItem.price}}</span>
<span class="collect">{{goodsItem.cfav}}</span>
</div>
</div>
</template>
<script>
export default{
name:"GoodsListItem",
props:{
goodsItem:{
type:Object,
default(){
return{}
}
}
}
methods:{
//图片加载
imageLode:{
this.$bus.$emit('itemImageLoad')
}
}
</script>
14.better-scrollf封装和使用
better-scroll
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<tittle>Tittle</tittle>
<style>
.content>{
height:200px;
background-color:red;
overflow:hidden;
}
</style>
</head>
<body>
<div>
<div class="content">
<ul>
<li>niha</li>
<li>niha</li>
<li>niha</li>
<li>niha</li>
</ul>
</div>
</div>
<scirpt src="./bscroll.js"></script>
<script>
const bscroll=newBScroll(ducoment.querySelector('.contenr'),{
probeType:3,
Click:true,
pullUpLoad:true
})
bscroll.on('pullingUp',()=>{
console.log('上拉加载更多');
setTimeout(()=>{
bscroll.finishPullUp()
},2000)
})
</script>
//common/Scroll.vue
<template>
<div class="wrapper" ref="wrapper">
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default{
name:"Scroll",
props:{
probeType:{
type:Number,
default:0
},
oullUpLoad:{
type:Boolean,
default:false
}
}
data(){
return{
scroll:null,
}
}
mounted(){
//1.创建BScroll对象
this.scroll=new BScroll(this.$refs.wrapper,{
click:true,
probeType:this.probeType,
pullUpLoad:this.pullUpload
})
//2.监听滚动的位置
this.scroll.on('scroll',(position)=>
this.$emit('scroll',position)
})
//3.监听上拉事件
this.scroll.on('pullingUp',()=>{
this.$emit('pillingUp')
})
},
methods:{
scrollTo(x,y,time=300){
this.scroll && this.scoll.scrollTo(x,y,time)
},
finishPullUp(){
this.scroll.finishPullUp()
},
refresh(){
//防止scroll.vue还没有加载就调用refresh
this.scroll && this.scroll.refresh()
}
finishPullUp(){
this.scroll && this.scroll.finishPullUp()
}
}
}
</script>
15.backtop的封装和使用
//BackTop.vue
<template>
<div class="back-top" @click="backClick">
<img src="assets/img/common/top.png" alt="">
</div>
</template>
<script>
export default{
name:"BackTop",
methods:{
backClick(){
}
}
}
</script>
<style scoped>
.back-top{
position:fixed;
right:8px;
bottom:55px;
}
</style>
监听一个组件的原生事件,需要加native
16.上拉加载更多
scroll.vue/home.vue
非父子组件通信:http://www.jb51.net/artivle/132371.html
17.刷新频繁的防抖函数处理
debounce() home.vue里面
可以在common建立util再写debounce
18.tabControl的offsetTop获取分析
this.$refs.topTabControl.currentIndex=index;
this.$refs.tabControl.currentIndex=index;
让两个TabControl的currentIndex保持一致
19.记录离开时home的状态
//App.vue
<template>
<div>
<keep-alive<</keep-alive>
</div>
</template>
//home.vue
computed:{
activated(){
this.$refs.scroll.scrollTo(0,this.saveY,0)
THIS.$refs.scroll.refresh()
},
deactivated(){
this.saveY=this.$refs.scroll.getScrollY
},
20.跳转到详情页
//GoodListItem.vue
<template>
<div class="goods-item @click="itemClick">
methods:{
itemClick(){
this.$router.push('/detail/'+this.goodsItem.iid)
}
//index.js
{
path:'/detail/:iid',
component:Detail
}
//detail.vue
<template>
<div id="detail">
<detail-nav-bar/ class="detailNav">
<scroll class="content" ref="scroll">
<detail-swiper :top-images="topImages"/>
<detail-base-info :goods="goods"/>
<detail-shop-info :shop="shop"/>
<detail-goods-info :detail-info="detailInfo"/>
<detail-param-info :param-info="paramInfo"/>
<detail-comment-info :comment-info="commentInfo"/>
<goods-list :goods="recommends"/>
<detail=bottom-bar/>
</scroll>
</div>
</template>
<script>
import DetailNavBar from './childComps/DetailNavBar'
import DetailSwiper from './childComps/DetailSwiper'
import DetailBaseInfo from './childComps/DetailBaseInfo'
import DetailShopInfo frim './shildComps/DetailShopInfo'
import DetailGoodsInfo from './childComps/DetailGoodsInfo'
import DetailParamsInfo from './childComps/DetailParamInfo'
import DetailCommentInfo from './shildComps/DetailCommentInfo'
import DetailBottomBar from './childComps/DetailBottomBar'
import Scroll from 'components/common/scroll/Scroll
import {getDetail, Goods} from "network/detail";
export default{
name:"Detail",
components:{
DetailNavBar,
DetailSwiper,
DetailBaseInfo,
DetailShopInfo,
Scroll,
DetailGoodsInfo,
DetailParamsInfo,
DetailCommentInfo,
DetailBottomBar
},
data(){
return{
iid:null,
topImages:[]
goods:{},
shop{},
detailInfo:{}
paramInfo:{},
itemParams:{},
commentInfo:{}
}
},
creater(){
//1.保存传入的iid
this.iid=this.$route.params.iid
}
//2.获取商品信息
this.goods=new Goods(data.itemInfo,data.colume.data.shopInfo.services)
//3.创建店铺信息的对象
this.shop=new Shop(data.shopInfa)
})
//4.保存商品详情数据
this.detailInfo=data.derailInfo;
}
//5.获取参数的信息
this.paramInfo=new GoodsParam(data.itemParams.info,data.itemParams.rule)
})
//6.取出参数的信息
this.itemParams=data.itemParams
//7.取出评论的信息
if(data.rate.cRate != 0){
this.commentInfo=data.rate.list[0]
}
},
methods:{
imageLoad(){
this.$refs.scroll.refresh()
}
</script>
<style scoped>
#detail{
position:relative;
z-indes:9;
background-color:#fff;
}
.detail-nav{
position:relative;
z-indes:9;
background-color:#fff;
}
.content{
height:calc(100%-44px);
}
</style>
21.详情页导航栏
//DetailNavBar.vue
<template>
<div>
<nav-bar>
<div slot="left" class="back" @click="backClick"
<div slot="center" class="tittle">
<div v-for="(item,index) in tittles"
class="tittle-item :class="active:index===currentIndex} @click="tittleClick(index)"">
{{item}}
</div>
</div>
</nav-bar>
</div>
</template>
<script>
import NarVar from 'components/common/navbar/NavBar'
export default{
name:"DetailNavBar",
components:{
NavBar
},
data(){
return{
tittles:['商品','参数','评论','推荐'],
currentIndex:0
}
},
methods:{
tittleClick(){
this.currenIndex=index;
},
backClick(){
this.$router.back()
}
}
}
</script>
<style scoped>
.tittle{
display:flex;
font-size:13px;
}
.tittle-item{
flex:1;
}
.active{
color:var(--color-high-text)
}
.back img{
margin-top:12px;
}
</style>
22.详情页请求数据以轮播图展示
//APP.vue
<keep-alive exclude="Detail">
<router-view/>
</keep-alive>
//network/detail.js
import {request} from "./request";
export function getDetail(iid){
return request({
url:'/detail',
params:{
iid
}
})
}
//detail.vue
<script>
import {{getDetail}} from"../../netwoek/detail";
</script>
created(){
getDetail(this.iid).then(res=>{
console.log(res);
})
}
上面是数据请求
//detailSwiper.vue 轮播图
<template>
<div class="detail-swiper">
<swiper class="swiper">
<swiper-item v-for="item in topImags">
</swiper>
<div>
<script>
import {Swiper,SwiperItem) from 'components/common/swiper'
export default{
name:"DetailSwiper",
components:{
Swiper,
SwiperItem
},
props:{
toImages:{
type:Array,
default(){
return[]
}
}
}
}
</script>
<style scoped>
.swiper{
height:300px;
overflow:hidden;
}
</style>
23.详情页信息
//detail.js 数据
export function getRecommend(){
return request({
url:'/recommend'
})
}
export class Goods{
constructor(itemInfo.colums,services){
this.tittle=itemInfo.tittle
this.desc=itemInfo.desc
this.newPrice=itemInfo.price
this.oldPrice=itemInfo.oldPrice
this.colums=colu,s
this.service=services
this.realPrice=itemInfo.lowNowPrice
}
}
export class Shop{
constructor(shopInfo){
this.logo=shopInfo.shopLoge;
this.name=shopInfo.name;
this.fans=shopInfo.cFans;
this.sells=shopInfo,cSells;
this.score=shopInfo.score;
this.goodsCount=shopInfo.cGoods
}
}
export class GoodsParam{
constructor(info,rule){
this.image-info.images?info.image[0] : '';
this.infos=info.set;
this.sizes=rule.tables;
}
}
//DetailBaseInfo.vue 价格
<template>
<div v-if="Object.keys(goods).length !== 0" class="base-info">
<div class="info-price">
<span class="n-price">{{goods.newPrice}}</span>
<span class="o-price">{{goods.oldPrice}}</span>
<span v-if"goods.discount" class="discount">{{goods.discount}}</span>
</div>
<div class="indo-service">
<span class="info-service-item" c-for="indev in goods.services.length-1" :key="index">
<img :src="goods.services[index-1].icon">
<span>{{goods.services[index-1].name}}</spam>
</span>
</div>
</div>
</template>
//DetailGoodInfo.vue 详细图片
<template>
<div v-if="Object.key(detailInfo).length !== 0" class=:goods-info">
<div class="info-desc clear-fix">
<div class="start">
</div>
<div class="desc">{{detailInfo.desc}}</div>
<div class="end"></div>
</div>
<div vlass="info-key">{{detailInfo.detailImage[0].key}}</div>
<div class-"info-list">
<img v-for="(item,index) in detailInfo.detailImage[0].list" :key="index" :src="item @load="imgLoad" alt-"">
</div>
</templata>
<script>
export default{
name:"DetailGoodsInfo",
props:{
detailInfo:{
type:Object
}
}
data(){
return{
counter:0,
imagesLenght:0
}
},
methods:{
imageLoad(){
//等所有加载完再回调一次
if(++this.counter === this.imageLength){
this.$emit('imageLoad')
}
}
},
watch:{
detailInfo(){
//获取图片个数
this.imageLength = this.detailInfo.detailImage[0].list.length
}
}
}
</script>
<style scoped>
.goods-info{
padding:20px 0;
border-bottom: 5px solid #f2f5f8;
}
</style>
//DetailParamInfo.vue 参数
<template>
<div class="param-info" v-if="Object.keys(paramInfo).length !==0">
<table v-for="(table,index) in paramInfo.sizes"
class="info-size" :key="index">
<tr v-for="(tr indey) in table" :key="indey">
<td v-for="(td,indez) in tr" :key="indez">{{td}}</td>
</tr>
</table>
<table class="info-param">
<tr v-for="(info,index) in paramInfo.infos">
<td class="info-param-key">{{info.key}}</td>
<td class="param-value">{{info.value}}</td>
</tr>
</table>
<div class="info-img"v-if="paramInfo.image.length !==0">
<img :src="paramInfo.image" alt="">
</div>
</template>
<script>
...
//DetailCommentInfo.vue 评论
<template>
...
</template>
<script>
import {formatDate} from "/common/utils";
export default{
name:"DetailCommentInfo",
props:{
commentInfo:{
type:Object,
default(){
return{}
}
}
},
filters:{
showDate(value){
//1.将事件戳转成Date对象
const date=new Date(value*1000)
//2.将Date进行格式化
return formatDate(date,"yyyy/MM/dd hh:mm:ss")
}
</script>
<style scoped>
...
</style>
//GoodsListItem.vue 商品推荐
<template>
<div class="goods-item" @click="itemClick">
<img :src="showImage" alt="" @load="imageLoad">
<div class="goods-info">
<p>{{product.tittle}}</P>
<span class="price">{{product.price}}</span>
<span class="collect">{{product.cfav>></span>
</div>
</div>
</template>
<script>
ecport default{
name:"GoodsListItem",
props:{
product:{
type:Object,
default(){
return{}
}
}
},
computed:{
showImage(){
return this.product.image || this.product.show.img
}
methods:{
imgLoad(){
this.$bus.$emit('itemImageLoad')
},
itemClick(){
this.$router.push({
path:'/detail',
query:{
iid:this.product.iid
}
})
}
}
</script>
..
mixin的使用
详情页导航栏的联动效果
//DetailNavBar.vue
data(){
return{
themeTopYs:[]
methods:{
tittleClick(index){
this.currentIndex=index;
this.$emit('tittleClick',index),
//Detail.vue
<detail-nav-bar @tittleClick="tittleClick"/>
<detail-params-info ref="params" :[aram-info="itemParams"/>
<detail-comment-info ref="comment" :comment-info="commentInfo"/>
<goods-list ref="recommend" :goods="recommends"/>
updated(){
this.themeTopYs.push(0);
this.themeTopYs.push(this.$refs.params.$el.offsetTop);
this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
this.themeTopYs.push(this.$refs.recommend.$el.offsetTop)
//2.或者,等待渲染$el,但是图片还没加载,所以数据不对
//this.$nextTick(()=>{
// this.themeTopYs.push(0);
// this.themeTopYs.push(this.$refs.params.$el.offsetTop);
// this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
// this.themeTopYs.push(this.$refs.recommend.$el.offsetTop)
//3.图片加载就给数据,加防抖
created(){
//给getThemeTopY赋值
this.getThemeTopY=debounce(()=>{
this.themeTopYs=[]
this.themeTopYs.push(0)
this.themeTopYs.push(this.$refs.params.$el.offsetTop);
this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
this.themeTopYs.push(this.$refs.recommend.$el.offsetTop);
},100)
},
methods:{
detailImageLoad(){
this.newRefresh()
this.getThemeTopY()
}
}
methods:{
tittleClick(index){
console.log(index);
this.$refs.scroll.scrollTo(0,-this.themeTopYs[200],100)
},
}
//Detail.vue 根据高度显示对应的主题
<detail-nav-bar @tittleClick="tittleClick" ref="nav"/>
coontentScroll(position){
//1.获取y值
const positionY=-position.y
let length = this.themeTopYs.length
//2.positionY和主题中值进行对比 index=0/1/2/3
let length =this.themeTopYs.length
for(let i =0; i<length; i++)
if(this.current!=i &&(i<length-1 && positionY >this.themeTops[i] && positionY < this.themeTopYs[i] || (i ===length-1 && positionY > this.themeTopYs[i])){
this.currentIndex=i;
this.$ref.nav.currentIndex=this.currentIndex;
}
//hack做法
加入最大值:this.themeTopTs.push(Number.MAX_VALUE)
let lenth=this.themeTopYs.length
for(let i=0; i<length-1; i++){
if(this.currentIndex !== i &&(positionY >= this.themeTopYs[i] && positionY <this.themeTopYs[i+1])){
this.currentIndex=i;
this.$ref.nav.currentIndex = this.currentIndex
}
}
//DetailBottomBar.vue 底部
<template>
<div class="bottom-bar">
<div>
<i class="icon service"></i>
<span class="text">客服<span>
</div>
<div>
<i class="icon select"></i>
<span class="text">收藏</span>
</div>
<div class="bar-item bar-right">
<div class="cart" @click="addToCart">加入购物车</div>
<div class="buy">购买</div>
</div>
</div>
</template>
<script>
export default{
name:"DetailBottomBar"
}
</script>
<style scoped>
.bottom-bar{
height:49px;
background:red;
position:relative;
bottom:49px;
}
</style>
//backTop的混入安装
//Detail.vue
//先导入插件和数据,变量,注册
<back-top @click.native="backTop" v-show="isShowBackTop"/>
import {itemListenerMixin,backTopMixin} from "common/mixin";
export default{
mixins:[itemListenerMixin,backTopMixin],
}
//mixin.js
export const itemListenerMixin={
data(){
return{
itemImgListener:null,
newRefresh:null
}
},
mounted(){
this.newRefresh=debounce(this.$refs.scroll.refresh,100)
this.itemImgListener=()={
this.itemImgListener=()=>
this.newRefresh()
}
this.$bus.$on('itemImgLoad',this.itemImgListener)
}
}
export const backTopMixin={
components:{
BackTop
},
data(){
return{
isShowBackTop:false
}
},
methods:{
backTop(){
this.$refs.scroll.scrollTo(0,0,300)
},
listenShoBack(position){
this.isShowBackTop=-position.y>BACK_POSITION
}
}
}
//加入购物车
//DetailBottomBar.vue
<div class="cart @click="addToCart">加入购物车</div>
methods:{
addToCart(){
this.$emit('addCart'}
}
//Detail.vue
<detail-bottim-bar @addCart="addToCart"/>
method:{
addToCart(){
const product={}
// 1.获取购物车需要展示的信息
product.image=this.TopImages[0];
product.tittle=this.goodsInfo.tittle;
product.desc=this.goodsInfo.desc;
product.price=this.goodsInfo.realPrice;
procuct.iid=this.iid;
//2.将商品添加到购物车
this.$store.dispatch('addCart',product).then(res=>{
this.$toast.show(res,2000);
})
}
//加购
//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装插件
Vue.use(Vuex)
//2.创建Store对象
const store=new Vuex.Store({
stateList:[]
},
mutations:{
addCounter(state,payload){
payload.count++
}
addToCart(state,payload){
state.cartList.push(payload)
}
}
action:{
addCart(context,payload){
//1.查找之前数组中是否有该商品
let product=context.state.cartList.find(item=>item.iid === payload.iid)
//2.判断oldProduct
if(product){
context('addCounter',oldProduct)
} else{
context.commit('addToCart',payload)
}
}
})
//3.挂在Vue实例
export default store
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip=false
//添加事件总线对象
Vue.prototype.$bus=newVue()
new Vue({
render:h=>h(App),
router,
store
}).$mount('#app')
//将index 重构
//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import action from './actions'
//1.安装插件
Vue.use(Vuex)
//2.创建Store对象
const state ={
cartList:[]
}
const store=new Vuex.Store({
state,
mutations,
actions
})
//3.挂载Vue实例上
export default store
...................................
//mutation.js
import{
ADD_COUNTER,
ADD_TO_CART
}from './mutaion-types'
export default{
[ADD_COUNTER](state,payload){
payload.count++
},
[ADD_TO_CART](state,payload){
state.cartList.push(payload)
}
}
...................................
//action.js
import{
ADD_COUNTER,
ADD_TO_CART
} from './mutation-types'
export default{
addCart(context,payload){
return new Promise(resolve,reject) => {
//1. 查找之前数组中是否有该商品
let oldProduct=context.state.cartList.find(item => item.iid ===payload.iid)
//2.判断oldProduct
if(oldProduct){
context.commit(ADD)COUNTER,oldProduct)
resolve('当前的商品数量+1')
}else{
payload.count = 1
context.commite(ADD_TO_CART,payload)
resolve('添加了新的商品')
}
})
}
}
......................................
//mutation.types.js
export const ADD_COUNTER='add_counter'
export const ADD_TO_CART='add_to_cart'
//cart.vue 购物车导航栏
<template>
<div class="cart">
<nav-bar class="nav-bar">
<div slot="center">购物车({{length}})</div>
</nav-bar>
</div>
<cart-list/>
<cart-bottom-bar/>
</template>
<script>
import NavBar from 'components/common/navBar/NavBar'
import CartList from './shildComps/CartList'
import CarBottomBar from './childComps/CartBottomBar'
import {mapGetters} from 'vuex'
export default{
name:'Cart',
components:{
NavBar,
CarBottomBar
},
computed:{
//两种语法
...mapGetters{['cartLength','cartList'])
length:'cartLength'
List:'cartList'
}
}
</script>
<style scoped>
.cart{
height:100vh;
}
.nav-bar{
background-color:var(--color-tint);
color:#fff;
}
</style>
.......................................
//getter.js
export default{
cartLength(state){
return state.cartList.length
},
cartList(state){
return state.cartList(0
}
}
//cartList 展示列表
<template>
//为加购再进入页面时刷新
<div class="cart-list" ref="scroll">
<scroll class=content">
<cart-list-item v-for="(item,index) in cartList" :key="index" :product="item"/>
</scroll>
</div>
</template>
<script>
import Scroll from 'components/common/scroll/Scroll'
import CsrtListItem from './CartListItem'
import {mapGetters} from 'vuex'
export default{
name: "CartList",
components:{
Scroll,
CartListItem
},
computed:{
...mapGetters(['cartList'])
}
//为加购再进入页面时刷新
activated(){
this.$refs.scroll.refresh()
}
}
</script>
<style scoped>
.cart-list{
height:cart(100%-44px-49px-40px);
}
.content{
height:100%;
overflow:hidden
}
</style>
........................................
//CartListItem 具体展示每一个数据
<template>
<div id="shop-item">
<div class="item-selector">
<checkButton @checkBtnClick="checkedChange" :value="itemInfo.checked" @click.native="checkClick"></checkButton>
</div>
<div class="item-img">
<img :src="itemInfo.image" alt="商品图片">
<div>
<div class="item-info">
<div class="item-tittle">{{itemInfo.tittle}}</div>
<div class="item-desc">{{itemInfo.desc}}</div>
<div class="item-count right">{{itemInfo.count}}</div>
<div class="info-button">
<div class="item-price left">¥{{itemInfo.price}}</div>
<div class="item-count right">{{itemInfo.count}}</div>
</div>
</div>
</div>
</template>
<script>
import CheckButton from './CheckButton'
export deafult{
name:"CartListItem",
props:{
product:{
type:Object,
default(){
return{}
}
}
}
methods:{
checkClick(){
this.itemInfo.checked= !this.itemInfo.checked
}
}
}
</script>
<style scoped>
#shop-item{
width:100%;
display:flex;
fon-size
...
</style>
//CheckButton.vue 小选中按钮
<template>
<div class="check-button" :class="{check:isChecked}">
<img src="~assert/img/cart/tick.svg" alt="">
</div>
</template>
<script>
export default{
name:"CheckButton",
props:{
isChecked:{
type:Boolean,
default:false
}
}
}
</script>
<style scoped>
.check-button{
border-radius:50%;
border: 2px solid #aaa;
}
.check{
border-coloe:red;
}
</style>
//设置是否选中
//mutation.js
[ADD_TO_CART](state,payload){
payload.checked=true
state.cartList.push([ayload)
}
//CartListItem.vue
<CheckButton :is-checked="itemInfo.checked"/>
//CarBottomBar.vue 商品底部结算
<template>
<div class="bottom-bar">
<div class="check-content">
<check-button :is-checked="isSelecAll" class="check-button" @click.native="checkClick"/>
<span>全选</span>
</div>
<div class="pricr">
合计:{{totalPrice}}
</div>
<div class="calculate">
去计算{{checkLength}}
</div>
</div>
</template>
<script>
import CheckButton from 'components/content/checkButton/checkButton'
export default {
name:"CartBottomBar",
components:{
CheckButton
},
computed:{
totalPrice(){
return '¥' +this.$tore.state.cartList.filter(item=>{
return item.checked
}).reducce((preValue,item)=>{
return preValue+item.price*item.count
},0).toFixed(2)
}
checkLength(){
return this.$store.state.carList.filter(item=>item.checked).length
},
isSelectAll(){
//return !(this.cartList.filter(item=>!item.checked).length)
if (this.cartList.length ===0 ) return false
return !this.cartList.find(item => !item.checked)
},
methods: {
checkClick(){
if(this.isSelectAll){
this.cartList.foreach(item => item.checked=false)
}else{
this.cartList.forEach(item => item.checked=true)
}
},
calcClick(){
if(!this.isSelectAll){
this.$toast.show('请选择购买的商品',2000)
}
}
}
</script>
<style scoped>
.bottom-bar {
height:40px;
backgroung-color:red;
position:relative;
display:flex;
line-height:40px;
}
.check-content {
display:flex;
align-items:center;
margin-left:10px;
}
.check-button {
weidth:20px;
height:20px;
line-height:20px;
margin-right:5px
}
.price{
margin-left:20x;
}
.calculate{
width:90px;
background:red;
color:#fff;
text-align:center
}
</style>
//封装toast
//Toast.vue
<template>
<div class="toast" v-show="show">
<div>{{message}}</div>
</div>
</template>
<script>
export default {
name:"Toast",
data(){
message:'',
isShow:false
}
},
methods:{
show(message='默认文字',duration=2000){
this.isShow=true;
this.message=message
setTimeout(() => {
this.isShow=false;
this.message =''
},duration)
}
}
}
</script>
<style scoped>
.toast{
position:fixed;
top:%;
left:%;
transfrom:translate(-50%,-50%);
padding:8px 10px;
z-index:999;
color:#fff;
background-dolor:rgba(0,0,0,.75);
}
</style>
//................................................................................
//toast/index.js
import Toast from './Toast'
const obj={}
obj.install=function(Vue){
//1.创建组件构造器
const toastConstrustor=Vue.extend(Toast)
//2.new的方式,根据组件构造器,可以创建出一个组件对象
const toast=new toastConstrustor()
//3.将组件对象手动挂载到某一个元素上
toast.$mount(document.createElement('div'))
//4.toast.$el对应的就是div
document.body.appendChild(toast.$el)
Vue.prototype.$toast=Toast;
}
//..........................................................................
//main.js
import toast from 'componnets/common/toast'
//安装toast插件
Vue.use(toast)
//........................................................................
//CartbottomBar.vue
methods:{
calcClick(){
if(!this.isSelectAll){
this.$totast.show('请选择购买的商品',2000)
}
}
}
fastclick 解决移动端300毫秒的延迟
polifill:做适配
npm install fastclick --save
//main.js
import FastClick from 'fastclick'
FastClick.attch(document.body)
vue-lazyload框架 图片懒加载
npm install vue-lazyload --save
//main.js
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad,{
loading:require(./assets/img/common/placeholder.png')
})
使用时: <img v-lazy="showImage" alt="" @load="imgLoad">
p2vw css单位转化插件
npm install postcss-px-to-viewport --save-dev
//postcss.config.js 对插件进行配置
module.exports={
plugins:{
autoprefixer: {},
"postcss-px-to-viewport": {
viewportWidth:375, //视窗的宽度,对应的时我们设计稿的宽度
viewportHeight:667, //视窗的高度,对应的是我们设计稿的高度(也可以不配置))
unitportUnitL'vw', //指定’px'转化为私闯单位,建议使用vw
selectorBlackList:['ignore','tab-bar','tab-bar-item'],
//指定不需要转换的类,
minPixelValue:1, //小于或等于‘1px'不转换为视窗单位
mediaQuery:false //允许在媒体查询中转换'px'
}
}
nginx - 项目在window的部署
打包:npm run build
https://nginx.org
ubuntn
响应式原理-依赖技术的分析和学习
发布订阅者模式