cruxf / studyvue Goto Github PK
View Code? Open in Web Editor NEWVue学习站点
Vue学习站点
一、安装:npm install moment --save
二、在main.js中导入:
import moment from 'moment'
Vue.prototype.$moment = moment
三、使用:let nowTime = this.$moment().format('Y-MM-DD')
关于这个插件的更多知识请点击这里
根据慕课网实战课程——Vue2.0实战带你开发去哪儿APP整理而来,里面的内容和源码都是和该课程息息相关,建议结合视频来食用效果更佳
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>第一个示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{message}}
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
message: "hello world"
}
})
setTimeout(function() {
vm.$data.message = "bye world";
}, 2000)
</script>
</body>
</html>
【解析】
数据驱动是Vue的一大亮点,从以上代码就能够得知Vue不需要去操作DOM,只需要操作数据即可。它基本的执行流程是这样的:首先创建一个实例,接着使用el
规定Vue实例负责管理或有权限操作的区域,然后在data
里面存储所需要的数据。
在定时器那块可以得知,通过 实例.$data.属性名 能够获取相应的数据并对它进行处理。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TodoList</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" placeholder="输入一些内容" v-model="message" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<li v-for="item of list">{{item}}</li>
</ul>
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
message: "",
list: []
},
methods: {
handleBtnClick() {
this.list.push(this.message);
this.message = "";
}
}
})
</script>
</body>
</html>
【解析】
这个栗子引出如下知识点:v-model、v-on、v-for和methods,其中v-model是实现双向数据绑定的一个指令,意思是:视图层(V)的数据变动能够与数据模型层(M)的数据关联在一起,从而实现了改变V层数据则M层数据也跟着改变,反之,M层数据的变动也会让V层的数据变动,有点同生共死的味道。
v-on则是定义事件的指令,比如v-on:click代表点击事件,v-on:mouseover鼠标滑过事件等,要想知道更多事件和其他v-on的知识,请点击这里传送门
v-for则是遍历数据的指令,一般语法为:alias in expression
或者alias of expression
,这两种遍历有什么区别以及更多的v-for知识,请点击这里传送门,十分的浅显易懂,需要明白是为什么遍历的时候需要加上:key
methods看字面意思就知道它是存储方法的一个集合。
上面的栗子执行过程很容易理解:首先将数据模型层的数据绑定在视图层中,接着改变视图层的数据,触发一个点击事件,然后在点击事件的方法中将从视图层返回到数据模型层的数据push进list数组之中从而遍历到视图层上。
最开始的热门开发模式为MVP模式,其中M代表数据模型层(Model)、V代表视图层(View)、P代表控制器层(Presenter),下面请看一段基于MVP的代码(M层代码近乎没有)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TodoList</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
</head>
<body>
<div id="app">
<input type="text" id="input" />
<button id="btn">提交</button>
<ul id="ul"></ul>
</div>
<script>
function Page() {
}
$.extend(Page.prototype, {
init: function() {
this.bindEvents()
},
bindEvents: function() {
var btn = $("#btn");
btn.on("click", $.proxy(this.handleBtnClick, this))
},
handleBtnClick: function() {
var inputElem = $("#input");
var inputValue = inputElem.val();
var ulElem = $("#ul");
ulElem.append('<li>' + inputValue + '</li>');
inputElem.val("");
}
})
var page = new Page();
page.init();
</script>
</body>
</html>
假如用以上模式来开发程序,那么我们大部分都在操作DOM。
MVVM开发模式,其中M也是代表了数据模型层,V代表了视图层,不同的是将P层替换成了VM 层,而这个VM层是Vue框架自带的一个视图模型层,无需我们来进行打理。这样我们开发的时候只要关注视图层和数据模型层即可,非常轻松愉快。更详细的资料请看这传送门
组件,这个词十分容易理解。只要把web应用程序,也就是网站当做成一栋摩天大楼,那么组件就是那一小块的砖头。利用组件化的**来开发一个网站,对于后期的维护是十分有利的,下面请看使用组件化**修改的TodoList代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TodoList</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="message" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<todo-item v-bind:content="item" v-for="item of list"></todo-item>
</ul>
</div>
<script>
// 定义一个子组件(全局子组件)
Vue.component("TodoItem", {
props: ['content'],
template: "<li>{{content}}</li>"
})
// 父组件区域
let vm = new Vue({
el: "#app",
data: {
message: "",
list: []
},
methods: {
handleBtnClick() {
this.list.push(this.message);
this.message = "";
}
}
})
</script>
</body>
</html>
上面是是定义了一个全局子组件,下面我们再来定义一个局部子组件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TodoList</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="message" />
<button v-on:click="handleBtnClick">提交</button>
<ul>
<todo-item v-bind:content="item" v-for="item of list"></todo-item>
</ul>
</div>
<script>
// 定义一个子组件(局部子组件)
var TodoItem = {
props: ['content'],
template: "<li>{{content}}</li>"
}
// 父组件区域
let vm = new Vue({
el: "#app",
data: {
message: "",
list: []
},
methods: {
handleBtnClick() {
this.list.push(this.message);
this.message = "";
}
},
components: {
"todo-item": TodoItem
}
})
</script>
</body>
</html>
和全局子组件的不同就是,局部子组件需要在父组件的实例中使用components进行注册方能使用。在这两段代码中,还涉及到了父组件向子组件传值这个知识点,其实也是挺好理解的:在视图层调用子组件之中,使用v-bind指令设置一个属性,并传入相关的值;然后子组件里使用props获得传入过来的属性,并将该属性渲染出来即完成了父子组件的传值。
子组件向父组件传值涉及到了一个专业名词,叫做发布订阅模式。这个名词接下来再解释,先看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<input v-model="inputValue" />
<button @click="handleSubmit()">提交</button>
<ul>
<!--调用子组件-->
<todo-item v-for="(item,index) of list" :key="index" :content="item" :index="index" @delete="handleDelete(index)">
</todo-item>
</ul>
</div>
<script>
//子组件(全局)
Vue.component('todo-item', {
props: ['content', 'index'],
template: '<li @click="handleClick()">{{content}}</li>',
methods: {
handleClick() {
//发布订阅模式
this.$emit('delete', this.index);
}
}
})
//父组件
new Vue({
el: "#root",
data: {
inputValue: '',
list: []
},
methods: {
handleSubmit() {
this.list.push(this.inputValue);
this.inputValue = '';
},
handleDelete(index) {
this.list.splice(index, 1);
}
}
})
</script>
</body>
</html>
【解析】
当子组件某个事件被触发时,那么子组件内部就会发布一个自定义事件和相对应的参数;此时父组件监听(也称为订阅)子组件自定义的事件,当该自定义事件被触发的时候,则在父组件里调用一个方法,实现删除的功能。
备注: 如果不在子组件添加对应的参数index,那么结果会是如何呢?如果在模板中这么定义:
@delete="handleDelete()"会发生什么情况呢?为什么?该如何修改才能达到这么定义:
@delete="handleDelete"的效果?
刚开始接触生命周期函数的人可能对这个概念十分迷糊,但是仔细观察和分析却会发现很容易理解。用大白话来说生命周期函数就是:在浏览器解析Vue实例的过程中自动执行的函数。比如你在methods中定义了一个方法,那么这个方法需要在视图层被相应的事件触发的时候才会执行,但是你将该方法放到生命周期函数中,则在Vue实例的某一个阶段该方法会被自动执行。
为了更好的理解生命周期函数在各个阶段发生的事情,请结合官方网站和我下面的大白话进行食用。
下面一段代码充分说明了beforeMount()和mounted()的区别:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">{{message}}</div>
<script>
let vm = new Vue({
el: "#app",
data: {
message: "hello vue.js"
},
beforeMount() {
console.log(this.$el);
console.log("beforeMount");
},
mounted() {
console.log(this.$el);
console.log("mount");
}
})
</script>
</body>
</html>
剩下的几个生命周期函数大家上官网看看就好啦,在这不一一说明,能看懂那些英语单词,自然也就能理解。其中触发beforeDestroy()和destroyed()函数很简单,只要在控制台输入vm.$destroy()
即可。对于beforeUpdate()和updated()只要在控制台更新了某些数据即可被触发。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{message}}
<div v-text="message"></div>
<div v-html="message"></div>
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
message: "<h1>hello Vue!</h1>",
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>计算属性、方法和侦听器</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{fullName}}
{{age}}
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三丰",
age: 22
},
//计算属性
computed: {
fullName(){
console.log("计算了一次");
return this.firstName + " " + this.lastName
}
}
})
</script>
</body>
</html>
为了测试computed的缓存特点,在data里新增了一个age数据进行对比,当在控制台更新age值的时候,fullName()并没有执行;当更新firstName和lastName值的时候,fullName()被执行,即证明了计算属性具有:当依赖的数据没有发生变化的话,那么数据就不会被重新渲染的缓存特点。
下面再看一个计算属性的栗子:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" Content="text/html; charset=utf-8" />
<title>javascript</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.delete {
text-decoration: line-through;
color: #ddd;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="inputValue" />
<button @click="addContent">添加内容</button>
<button @click="cleanContent">删除完成内容</button>
<ul>
<li v-for="(item,index) of list" @click="toggle(index)" :class="{delete:item.state}">
{{index}}:{{item.mes}}
</li>
</ul>
<p>完成条数:{{Complete}}/{{this.list.length}}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
inputValue: '',
list: [
{ mes: '王者农药', state: false },
{ mes: '荒野行动', state: false },
{ mes: '地下城', state: false }
]
},
computed: {
Complete() {
return this.list.filter(function(v) {
return v.state
}).length
}
},
methods: {
addContent() {
this.list.push({
mes: this.inputValue,
state: false
})
this.inputValue = ''
},
toggle(index) {
this.list[index].state = !this.list[index].state
},
cleanContent() {
this.list = this.list.filter(function(v) {
return !v.state
})
}
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>计算属性、方法和侦听器</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{fullName()}} {{age}}
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三丰",
age: 22
},
// 方法
methods: {
fullName() {
console.log("计算了一次");
return this.firstName + " " + this.lastName;
}
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>计算属性、方法和侦听器</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{fullName}} {{age}}
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三丰",
fullName: "张三丰",
age: 22
},
// 侦听器
watch: {
firstName() {
console.log("计算了一次");
this.fullName = this.firstName + this.lastName;
},
lastName() {
console.log("计算了一次");
this.fullName = this.firstName + this.lastName;
}
}
})
</script>
</body>
</html>
可以在控制台输入命令vm.$data.firstName = 'jack'
看看最后结果如何,还有就是firstName()和lastName()的命名方式可是固定的,不是随意的。
所有的计算属性都以函数的形式写在vue实例内的computed选项内,最终返回计算的结果 。每一个计算属性都包含一个getter和一个setter,我们的上个实例都是计算属性的默认用法,只是利用了getter来获取,在你需要时,也可以提供一个setter函数,当手动修改计算属性的值就像修改一个普通数据那样时,就会触发setter函数,执行一些自定义操作 ,请看如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>计算属性getter和setter</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{fullName}}
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三丰'
},
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName;
},
set(newValue) {
var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
}
})
</script>
</body>
</html>
绝大多数情况下,我们只会用默认的getter方法来读取一个计算属性,在业务开发中很少用到setter,所以在声明一个计算属性时,可以直接使用默认的写法,不必将getter和setter声明。
下面是几种常见的Vue样式绑定方法,看源码进行测试就可以了:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue样式绑定</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.activated {
color: green;
}
.red {
color: red;
}
.fontSize {
font-size: 20px;
}
</style>
</head>
<body>
<div id="app">
<!--class的对象绑定-->
<div @click="handleDivClick" :class="{activated:isActivated}">
hello vue.js
</div>
<!--class的数组绑定-->
<div @click="handleDivClickOne" :class="[activatedOne,activatedTwo]">
hello vue.js
</div>
<div @click="handleDivClickOne" :class="[index==tabIndex?'item-active':'']">
hello vue.js
</div>
<!--class的style绑定1-->
<div :style="styleObj" @click="handleDivClickTwo">
hello vue.js
</div>
<!--class的style绑定2-->
<div :style="[styleObjOne,{fontSize:'30px'}]">
hello vue.js
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
isActivated: true,
activatedOne: "",
activatedTwo: "fontSize",
styleObj: {
color: "yellow"
},
styleObjOne: {
color: "black"
}
},
methods: {
handleDivClick() {
this.isActivated = !this.isActivated;
},
handleDivClickOne() {
// 可以简化成三元运算符
if(this.activatedOne === "red") {
this.activatedOne = ""
} else {
this.activatedOne = "red"
}
},
handleDivClickTwo() {
this.styleObj.color = this.styleObj.color === "yellow" ? "black" : "yellow"
}
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue条件渲染</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div v-if="lookMeOne">{{messageOne}}</div>
<div v-show="lookMeTwo">{{messageTwo}}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
lookMeOne: true,
lookMeTwo: false,
messageOne: "hello vue.js",
messageTwo: "hello vue.js"
}
})
</script>
</body>
</html>
运行以上代码,在浏览器的控制台上通过改变lookMeOne和lookMeTwo的值观察并分析v-if和v-show的区别。
根据以上分析可以得知,如果要经常切换某个元素显示或者隐藏,那么使用v-show无疑是最好的选择,能让网站性能提高。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue条件渲染</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div v-if="lookMe">{{messageOne}}</div>
<div v-else>{{messageTwo}}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
lookMe: false,
messageOne: "hello vue.js",
messageTwo: "hello world"
}
})
</script>
</body>
</html>
更多的条件渲染内容,比如key值的作用请查看官方网站,我有些说不清楚额。
列表渲染,无非是v-for指令的作用,在使用时我们需要记住的是不要漏了书写独一无二的key值来提高渲染效率,并且应该使用of
替换v-for里面的in
,因为for in会遍历原型上的属性。还有就是假如要修改遍历数组里的数据,记得使用变异的方法来修改,否则修改后的数据Vue是没有办法正确检测的。下面请看一段代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue条件渲染</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div v-for="(item,index) of list" :key="item.id">
{{item.text}}--{{index}}
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
list: [
{ id: "001", text: "这是第一项数据" },
{ id: "002", text: "这是第二项数据" },
{ id: "003", text: "这是第三项数据" }
]
}
})
</script>
</body>
</html>
添加数据时在控制台输入这样的指令:vm.list.push({id:"004",text:"这是第四项数据"}),能正确被Vue实例检测到,并在页面显示;当在控制台输入这样的指令:vm.list[4] = {id:"004",text:"这是第四项数据"},不能被Vue实例检测到,并且页面也没有任何的显示。
修改数据时在控制台输入这样的指令:vm.list[1] = {id:"002",text:"这是被修改的第二项数据"},不能被Vue实例检测到,并且页面也没有任何的显示;当在控制台输入这样的指令:vm.list.splice(1,1,{id:"002",text:"这是被修改的第二项数据"}),那么就能正确被Vue实例检测到,并在页面显示被修改的数据。
当然,直接修改数组的引用,那么数据也会被Vue正确检测并显示在页面,比如在控制台输入这样的指令:
vm.list = [
{ id: "001", text: "这是第一项数据" },
{ id: "002", text: "这是被修改的第二项数据" },
{ id: "003", text: "这是第三项数据" }
]
在下面那段代码中,template是没有被渲染出来的,假如template替换成div元素,那结果就会不同。
<div id="app">
<template v-for="(item,index) of list" :key="item.id">
<div>
{{item.text}}--{{index}}
</div>
<span>{{item.text}}</span>
</template>
</div>
对于使用v-for遍历数组和遍历对象,其中是有些区别的,我认认真真的正常速度听了老师的课程之后,再去看官方网站的教程,真的是跟发现新大陆一样,希望你们也能如此,不脱离官网,反复阅读和研究。
之前改变对象里的数据时只有这么一种办法,先看看代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue条件渲染</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div v-for="(item,key,index) of userInfo">
{{item}}--{{key}}--{{index}}
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
userInfo: {
name: "jack",
age: 28,
gender: "male",
salary: "secret"
}
}
})
</script>
</body>
</html>
之前添加数据的方法就是在控制台输入指令:
vm.userInfo = {
name: "jack",
age: 28,
gender: "male",
salary: "secret",
address: "北京"
}
其实Vue提供了一个set()用来添加数据,在控制台输入指令:Vue.set(vm.userInfo,"address","北京")即可以往userInfo对象里添加一个属性数据。
通过实例也能够往对象里添加数据,在控制台输入指令:vm.$set(vm.userInfo,"address","北京")。
我们还能够通过set()来修改数组内的数据,请先看下面的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的set()</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div v-for="(item,index) of list">
{{item}}
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
list: [1, 2, 3, 4, 5]
}
})
</script>
</body>
</html>
在控制台输入指令:Vue.set(vm.list,1,66)就能够把数组第二项的内容变成"66";当然在控制台输入这样的指令也是可以的:vm.$set(vm.list,1,66)。
当组件为一个和父标签有特殊关联的时,直接导入子组件会让浏览器解析异常,比如下面的代码,会把row组件渲染在table标签之外:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue组件使用中的细节点</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
<script>
Vue.component("row", {
template: "<tr><td>this is a row</td></tr>"
})
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
造成这种问题的原因是由于父子标签之间的联系导致,只要使用is
关键字将组件以另外一种方式导入即可:
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
会有这种问题出现的还有ul和li标签,dl和dt标签,select和option标签等,解决的办法都是通过is
关键字。
还有一个小细节就是在根组件中,data可以返回一个对象,但是在子组件中data却必须以函数的形式在内部返回一些数据,这么设计的目的是为了避免N个子组件彼此之间数据受到污染、互相影响,请看核心代码:
Vue.component("row", {
data(){
return {
content: "this is a row"
}
},
template: "<tr><td>{{content}}</td></tr>"
})
虽然Vue不建议我们通过DOM来操作页面,但是当业务复杂的时候,只能通过关键字ref
来对DOM进行操作实现相应的功能,以下代码是获得html元素的DOM:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue组件使用中的细节点</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div ref="hello" @click="handleClick">
hello world
</div>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
},
methods: {
handleClick() {
// this.$refs指的是页面所有的ref引用
// 在控制台查看输出结果是什么
console.log(this.$refs.hello);
console.log(this.$refs.hello.innerHTML);
}
}
})
</script>
</body>
</html>
但是当我们需要获得一个组件的DOM时,也可说是组件的引用时,应该怎么做呢?下面看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue组件使用中的细节点</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<div>{{total}}</div>
</div>
<script>
Vue.component("counter", {
template: "<div @click='handleClick'>{{number}}</div>",
data() {
return {
number: 0
}
},
methods: {
handleClick() {
this.number++;
this.$emit("change")
}
}
})
var vm = new Vue({
el: "#app",
data: {
total: 0
},
methods: {
handleChange() {
// 控制台看看下面代码输出的结果并加以分析
console.log(this.$refs.one.number);
console.log(this.$refs.two.number);
// 对total进行累加
this.total = this.$refs.one.number + this.$refs.two.number
//this.total++ 这个也可以
}
}
})
</script>
</body>
</html>
下面我们来看一个开发需求:在一个input输入框中,输入金额并且保留两位小数。这个需求看似简单,然而牵涉的东西却很多,比如数据需要使用v-model双向数据绑定,那么可以像下面这么写吗:
<li class="border-1px">
贷款金额(元):
<input
type="text"
placeholder="请填写贷款金额"
:value="MiddleInfo.loanAmount | Tofixed"
v-model="MiddleInfo.loanAmount"
/>
</li>
事实上这是有问题的,因为v-model和v-bind指令在这里面会起错误冲突。但是不这么写的话那么过滤器的方法就无法被调用了,具体可以查看官网,很明确的说明了过滤器的使用地方是在“插值中或者v-bind指令中”。
那么我们该如何解决上面的问题呢?此时我们需要的只是将v-model指令去掉,使用@chang事件来进行代替,然后我们用关键字ref来获取DOM的数据。具体实现过程请看下面代码:
//html结构层
<li class="border-1px">
贷款金额(元):
<input
type="text"
placeholder="请填写贷款金额"
:value="MiddleInfo.loanAmount | Tofixed"
@change="onChangeAmount"
ref="AmountValue"
/>
</li>
// 金额过滤器
<script>
filters: {
Tofixed(value) {
value = Number(value);
return value.toFixed(2);
}
}
// 金额改变触发事件
onChangeAmount() {
this.MiddleInfo.loanAmount = this.$refs.AmountValue.value;
}
</script>
这一块曾经专门开了个仓库来介绍,先到这里熟悉熟悉传送门,然后再回来。课程中老师讲到几个新的知识点,记录一下。
解决数据流单向问题:将父组件传递过来的数据复制一份出来重新使用,下面请看源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>父子组件传值</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
<script>
var counter = {
props: ["count"],
data() {
return {
number: this.count
}
},
template: "<div @click='handleClick'>{{number}}</div>",
methods: {
handleClick() {
// 下面为错误示范
// this.count ++
// 下面为正确示范
this.number++
}
}
}
var vm = new Vue({
el: "#app",
data: {
total: 0
},
components: {
counter
}
})
</script>
</body>
</html>
默认已经熟悉了前面传送们的内容,直接源码走起:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>父子组件传值</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<counter :count="0" @inc="handleIncrease"></counter>
<counter :count="0" @inc="handleIncrease"></counter>
<div>{{total}}</div>
</div>
<script>
// 子组件区域
var counter = {
props: ["count"],
data() {
return {
number: this.count
}
},
template: "<div @click='handleClick'>{{number}}</div>",
methods: {
handleClick() {
this.number = this.number + 2;
this.$emit("inc", 2)
}
}
}
// 父组件区域
var vm = new Vue({
el: "#app",
data: {
total: 0
},
methods: {
handleIncrease(val) {
this.total = this.total + val
}
},
components: {
counter
}
})
</script>
</body>
</html>
当父组件向子组件传递参数(属性)的时候,子组件有权利对传递过来的参数进行校验,下面请看一些常用的组件参数校验方式:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件参数校验与非props特性</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child content="12345678"></child>
</div>
<script>
// 子组件区域
Vue.component("child", {
props: {
// 参数值为字符串型
content: String,
// 参数值为数字型
content: Number,
// 传数字和字符串都行
content: [Number, String],
// 复杂的参数检验
content: {
// 参数值为一个字符串型
type: String,
// required值为true表示content这个参数为必须传递
required: false,
// 当没有传入content参数时,默认content参数值为"default value"
default: 'default value',
// 校验传入参数的长度
validator(value) {
return(value.length > 5)
}
}
},
template: "<div>{{content}}</div>"
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
非props特性与props是相反的,主要有两点不同之处:1、不使用关键字props
接收父组件的值;2、父组件调用子组件中定义的属性会被浏览器渲染出来。下面请看源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件参数校验与非props特性</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child content="12345678"></child>
</div>
<script>
// 子组件区域
Vue.component("child", {
template: "<div>hello vue.js</div>"
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
在父组件调用子组件的时候,如果直接在调用子组件的地方使用@click="handleClick"
其实是不会触发handleClick方法的,原因是此时的click是一个自定义的事件,结合父子组件传值就能大概明白什么意思。
<div id="app">
<child @click="handelClick"></child>
</div>
在子组件绑定原生事件一共有两种方式,一种是直接在子组件内部定义,如下面代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>给子组件绑定原生事件</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child></child>
</div>
<script>
// 子组件区域
Vue.component("child", {
template: "<div @click='handleChildClick'>hello vue.js</div>",
methods: {
handleChildClick() {
console.log("子组件内部事件被触发")
}
}
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
另外一种方式就是直接在调用子组件的地方使用native
关键字,如下面代码所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>给子组件绑定原生事件</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child @click.native="handleClick"></child>
</div>
<script>
// 子组件区域
Vue.component("child", {
template: "<div>hello vue.js</div>"
})
// 父组件区域
var vm = new Vue({
el: "#app",
methods: {
handleClick() {
console.log("点击事件被触发")
}
}
})
</script>
</body>
</html>
还是之前的那篇总结,大家先来这里看看哈传送门,课程里面的内容这次看得还是有些晕乎,暂时不做讲解,看传送门的内容和结合下面的源码就好了:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>非父子组件间传值(Bus/总线/发布订阅模式/观察者模式)</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child content="One"></child>
<child content="Two"></child>
</div>
<script>
// 在Vue实例的原型中新增一个属性bus作为中转站
Vue.prototype.bus = new Vue()
// 子组件区域
Vue.component("child", {
data() {
return {
selfContent: this.content
}
},
props: {
content: String
},
template: "<div @click='handleClick'>{{selfContent}}</div>",
methods: {
handleClick() {
this.bus.$emit("change", this.selfContent)
}
},
mounted() {
var self = this;
this.bus.$on("change", function(msg) {
self.selfContent = msg
})
}
})
// 父组件区域
var vm = new Vue({
el: "#app",
methods: {
handleClick() {
console.log("点击事件被触发")
}
}
})
</script>
</body>
</html>
为什么要在Vue里面使用插槽?插槽到底是什么来的?带着这两个问题,先来看看插槽的使用场景:在父组件调用子组件的时候,想要从父组件传递一些元素和内容与子组件合并,此时当然可以利用父子组件传值的方式实现这个功能,比如<child content="<p>123</p>"></child>
,但是这种方式十分不容易管理,并且当要传递的内容过多时,该怎么办?此时我们的插槽就隆重登场啦!下面请看源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中使用插槽(slot)</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<h2>hello world</h2>
</child>
</div>
<script>
// 子组件区域
Vue.component("child", {
template: `<div>
<span>hello vue.js</span>
<slot>默认内容</slot>
</div>`
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
【分析】
在子组件中使用插槽slot引入父组件传递过来的<h2>hello world</h2>
,那么就能够将传入过来的元素和内容进行渲染,并且slot还有个特点,就是:当没有<h2>hello world</h2>
或其他传入元素的时候,slot会调用内部设置好的默认值。
具名插槽
能够分别识别多个从父组件传入进来的元素,将其当做组件一般在子组件里面进行调用,请看下面的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中使用插槽(slot)</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<div slot="header">这是头部区域</div>
<div slot="footer">这是底部区域</div>
</child>
</div>
<script>
// 子组件区域
Vue.component("child", {
template: `<div>
<slot name="header"><h1>默认值</h1></slot>
<span>hello vue.js</span>
<slot name="footer"><h1>默认值</h1></slot>
</div>`
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
运行上面的代码,即可明白具名插槽的作用是啥。
一谈到作用域,首先让我想到的就是各个组件的相互独立、互不干扰问题。果不然奇然,作用域插槽就是为了让不同父组件调用子组件时显示不同的形态,意思就是子组件的相关形态并不是它自己来决定的,而是由调用它的父组件来决定,下面请看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的作用域插槽</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<template slot-scope="props">
<h5>{{props.content}}</h5>
</template>
</child>
</div>
<script>
// 子组件区域
Vue.component("child", {
data() {
return {
list: [1, 2, 3, 4, 5, 6]
}
},
template: `<div>
<slot v-for="item of list" :content="item"></slot>
</div>`
})
// 父组件区域
var vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
当然还有很多关于作用域插槽的不同操作,虽然手法可能有些不同,但是它的执行本质却是一毛一样的。
动态组件在Vue中的意思是:一个元素(component
)将所有要被调用的组件包裹在其中,然后根据关键字is
绑定的数据的变化分别将集合中的组件渲染出来,下面请看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的动态组件</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<component :is="type"></component>
<!--<child-one v-if="type==='child-one'"></child-one>
<child-two v-if="type==='child-two'"></child-two>-->
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component("child-one", {
template: "<div>child-one</div>"
})
Vue.component("child-two", {
template: "<div>child-two</div>"
})
var vm = new Vue({
el: "#app",
data: {
type: 'child-one'
},
methods: {
handleBtnClick() {
this.type = (this.type === 'child-one' ? 'child-two' : 'child-one')
}
}
})
</script>
</body>
</html>
v-once指令
这个指令的作用就是将初次渲染的静态文件存储在内存中,第N次调用该文件的时候就直接从内存中拿出来即可,而不用重新渲染这个文件,从而提高了渲染效率和性能。
在解析动画原理之前,先看看下面一副图
通过这个图我们能够发现Vue动画的执行过程总共分为三个阶段(页面从隐藏到显示状态)
fade-enter
和fade-enter-active
这两个类;fade-enter-to
这个类同时删除fade-enter
这个类;fade-enter-active
和fade-enter-to
这两个类;fade
这个关键字是transition元素对应的name值,默认的name值是v。下面来看一段代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中CSS动画原理</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition name="fade">
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
【解析】
上面的代码执行过程:首先,在动画执行之前,系统自动添加了fade-enter
这个类,此时div元素的opcity的值为0,也就是在页面完全透明,接着动画执行之后,fade-enter
这个类被删除, 此时div元素的opcity值恢复默认值为1,fade-enter-active
这个类从始至终都在监视着opacity这个属性值的变化,当这个值发生变化时,动画效果则执行。
得知了元素从隐藏到显示的动画执行过程,那么现在来看看元素从显示到隐藏执行过程是如何的,先看一看下方一副图
通过这幅图,我们同样能够得知页面从显示到隐藏动画的执行过程也分为三个阶段
fade-leave
和fade-leave-active
这两个类;fade-leave-to
这个类同时删除fade-leave
这个类;fade-leave-active
和fade-leave-to
这两个类。下面结合一段代码进行分析
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中CSS动画原理</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 2s;
}
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition name="fade">
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
【解析】
很明显,在显示到隐藏阶段中,并没有定义fade-leave
这个类,原因很简单:因为此时fade-leave
这个类里面的属性和值等于页面显示的属性和值,即opacity为1,所以无需去定义fade-leave
这个类的属性与值,而只需在隐藏页面的时候添加fade-leave-to
这个类并且将opacity的值设为0即可。之后fade-leave-active
这个类的内部transition属性监测到了属性opacity的变化,因此延迟动画执行。
和CSS3一样,除了能在Vue中使用transition动画,animation动画也能够使用,使用方式如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中使用animate.css库</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
.fade-enter-active {
/*定义视图被置于 X和Y轴的何处*/
transform-origin: left center;
animation: bounce-in 2s;
}
.fade-leave-active {
/*定义视图被置于 X和Y轴的何处*/
transform-origin: left center;
animation: bounce-in 2s reverse;
}
</style>
</head>
<body>
<div id="app">
<transition name="fade">
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
【解析】
上面代码的执行过程是这样的:页面从显示到隐藏是先扩大(scale)1倍,接着扩大1.5倍,最后扩大0倍;而页面从隐藏到显示则动画效果完全反过来,即先扩大0倍,接着扩大1.5倍,最后扩大为1倍。
由于在transition标签中能够自定义class类,这是各种为Vue服务的css动画框架出现的基础,先看看如何自定义class类
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中使用animate.css库</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
.active {
/*定义视图被置于 X和Y轴的何处*/
transform-origin: left center;
animation: bounce-in 2s;
}
.leave {
/*定义视图被置于 X和Y轴的何处*/
transform-origin: left center;
animation: bounce-in 2s reverse;
}
</style>
</head>
<body>
<div id="app">
<transition
name="fade"
enter-active-class="active"
leave-active-class="leave"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
前面使我们自定义的动画,为了节省开发时间与精力,我们可以使用一个CSS动画框架,叫做Animate.css。我们先来官方网站进行下载和查看效果,接着我们就可以进行使用了,下面请看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中使用animate.css库</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="animate.css" />
</head>
<body>
<div id="app">
<transition
name="fade"
enter-active-class="animated swing"
leave-active-class="animated shake"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
在Vue中使用animate.css是不是十分简单?只要引入框架源文件,定义如下的语法即可运行成功:
<transition name="fade" enter-active-class="animated swing" leave-active-class="animated shake">
假如想要页面在刚开始加载的时候就有动画效果,那么该如何实现呢?下面请看代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中同时使用过渡和动画</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="animate.css" />
</head>
<body>
<div id="app">
<!--appear意思是这个属性刚出现的时候会有动画效果-->
<transition
name="fade"
appear
enter-active-class="animated swing"
leave-active-class="animated shake"
appear-active-class="animated shake"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
假如在执行animation动画的同时也执行transition动画(也成为过渡),那么该如何实现呢?下面请看代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中同时使用过渡和动画</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="animate.css" />
<style>
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 3s;
}
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity 3s;
}
</style>
</head>
<body>
<div id="app">
<!--appear意思是这个属性刚出现的会有动画效果-->
<transition
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated shake"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
上面代码成功实现了既有animation动画效果,也有transition过渡效果。然而却有一个执行时间的问题,在animate.css框架中,这个时间默认是1s,然而在transition中定义的却是3s,那么该如何让transition的执行时间为统一值,只需这么做即可:
<transition
type="transition"
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated shake"
>
当然也能自己定义动画执行的统一时间,代码如下
<transition
:duration="10000"
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated shake"
>
在代码执行的时候,我们可以打开控制台查看相应类的变化和效果的变化。并且我们可以把duration设置的更复杂一点点,如
<transition
:duration="{enter:5000,leave:10000}"
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated shake"
>
在Vue中使用JavaScript动画时,首先得了解动画钩子函数,这一块自行上网搜索了解哈,和Vue的生命周期本质可以说是一毛一样了。下面我们来看一个简单的JS动画效果案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的JS动画与Velocity.js</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<transition
name="fade"
@before-enter="handleBeforEnter"
@enter="handleEnter"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
},
handleBeforEnter(el){
el.style.color = "red"
},
handleEnter(el,done){
setTimeout( () => {
el.style.color = "green"
},2000)
}
}
})
</script>
</body>
</html>
细心的你们应该发现了在handleEnter这个方法中,done并没有被使用到。其实这个关键字很重要,调用它的时候,才能告诉程序这个动画执行完了,那么after-enter这个动画钩子函数才会开始生效,下面请看代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的JS动画与Velocity.js</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<transition
name="fade"
@before-enter="handleBeforEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
},
handleBeforEnter(el) {
el.style.color = "red"
},
handleEnter(el, done) {
setTimeout(() => {
el.style.color = "green";
}, 2000)
setTimeout(() => {
done()
}, 4000)
},
handleAfterEnter(el) {
el.style.color = "black"
}
}
})
</script>
</body>
</html>
以上动画效果都是原生的js动画,并且都是页面从隐藏到显示的动画效果,假如我们需要从显示到隐藏的动画效果该怎么做,很简单,只要把enter替换成leave即可。为了实现更复杂的js动画,我们可以使用js动画库——Velocity.js
使用该库还是老方法,先到官方网站进行下载,然后导入到项目之中,接下来就看看简单的一个案例实现吧
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的JS动画与Velocity.js</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="velocity.js"></script>
</head>
<body>
<div id="app">
<transition
name="fade"
@before-enter="handleBeforEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"
>
<div v-if="show">hello vue.js</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
},
handleBeforEnter(el) {
el.style.opacity = 0;
},
handleEnter(el, done) {
Velocity(el, {
opacity: 1
}, {
duration: 2000,
complete: done
})
},
handleAfterEnter(el) {
el.style.color = "red"
}
}
})
</script>
</body>
</html>
想要了解更多的velocity.js知识,请看它的中文文档
在Vue中有个内部机制:就是当一个元素被调用了,会被储存起来,那么下次重复调用的就能够直接拿过来用了,这样虽然提升了性能,但是也是由于这样导致动画效果在N个元素件切换的时候会失效,解决办法只要增加一个key值即可,代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中多个元素或组件的过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.v-enter {
opacity: 0;
}
.v-enter-active {
transition: opacity 1s;
}
.v-leave-active {
transition: opacity 1s;
}
.v-leave-to {
opacity: 0;
}
</style>
</head>
<body>
<div id="app">
<transition>
<div v-if="show" key="hello">hello vue.js</div>
<div v-else key="world">hello world</div>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
以上代码执行之后虽然动画效果有了,然而却是十分的丑陋,为了优化效果,我们可以在transition内添加mode属性,该属性有两个值,代入进去你会发现神奇的效果,分别是
下面来看组件间的过渡动画效果,需要借助之前学到的“动态组件”知识,当然不借助也是可以的,下面来看代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中多个元素或组件的过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.v-enter {
opacity: 0;
}
.v-enter-active {
transition: opacity 1s;
}
.v-leave-active {
transition: opacity 1s;
}
.v-leave-to {
opacity: 0;
}
</style>
</head>
<body>
<div id="app">
<transition mode="out-in">
<child-one v-if="show"></child-one>
<child-two v-else></child-two>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
Vue.component("child-one", {
template: "<div>child-one</div>"
})
Vue.component("child-two", {
template: "<div>child-two</div>"
})
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
使用动态组件实现该动画效果会简洁很多,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中多个元素或组件的过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.v-enter {
opacity: 0;
}
.v-enter-active {
transition: opacity 1s;
}
.v-leave-active {
transition: opacity 1s;
}
.v-leave-to {
opacity: 0;
}
</style>
</head>
<body>
<div id="app">
<transition mode="out-in">
<component :is="type"></component>
</transition>
<button @click="handleClick">点我</button>
</div>
<script>
Vue.component("child-one", {
template: "<div>child-one</div>"
})
Vue.component("child-two", {
template: "<div>child-two</div>"
})
var vm = new Vue({
el: "#app",
data: {
type: "child-one"
},
methods: {
handleClick() {
this.type = (this.type==="child-one"?"child-two":"child-one")
}
}
})
</script>
</body>
</html>
在列表遍历循环中,其实不建议让:key="index",因为这会导致性能上的下降同时功能方面的问题也有影响,所以能不用index作为key的值就不要用。好了,说正事,为了让列表在遍历的时候有过渡效果,只要使用关键字transition-group
即可,下面我们来看代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中的列表过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.v-enter {
opacity: 0;
}
.v-enter-active {
transition: opacity 2s;
}
.v-leave-to {
opacity: 0;
}
.v-leave-active {
transition: opacity 2s;
}
</style>
</head>
<body>
<div id="app">
<transition-group>
<div v-for="(item,index) of list" :key="item.id">
{{item.title}}
</div>
</transition-group>
<button @click="handleClick">点我</button>
</div>
<script>
var count = 0;
var vm = new Vue({
el: "#app",
data: {
list: []
},
methods: {
handleClick() {
this.list.push({
id: count++,
title: "hello vue.js"
})
}
}
})
</script>
</body>
</html>
为了提高动画的复用性,我们可以为一个动画进行封装。封装的原理主要是使用到插槽和js动画,下面我们一起来看源码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue中封装动画</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<fade :show="show">
<div>hello vue.js</div>
</fade>
<fade :show="show">
<h1>hello vue.js</h1>
</fade>
<button @click="handleClick">点我</button>
</div>
<script>
Vue.component("fade", {
props: ["show"],
template: `<transition @before-enter="handleBeforEnter" @enter="handleEnter">
<slot v-if="show"></slot>
</transition>`,
methods: {
handleBeforEnter(el) {
el.style.color = "red"
},
handleEnter(el, done) {
setTimeout(() => {
el.style.color = "green";
done()
}, 2000)
}
}
})
var vm = new Vue({
el: "#app",
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
</body>
</html>
很简单的一个需求,直接看下面代码即可明白
export default {
name: 'Register',
data () {
return {
codeTime: 10,
codeContent: '发送验证码',
codeClick: true
};
},
methods: {
// 获得手机验证码
sendCode() {
// 倒计时
if (!this.codeClick) return
this.codeClick = false
this.codeContent = this.codeTime + 's后重新发送'
let clock = window.setInterval(() => {
this.codeTime--
this.codeContent = this.codeTime + 's后重新发送'
if (this.codeTime < 0) {
window.clearInterval(clock)
this.codeContent = '重新发送验证码'
this.codeTime = 10
this.codeClick = true
}
},1000)
// 接口请求
let datas = {
mobile: this.mobile,
prefixe: this.prefixe,
captcha_data: this.captcha_data
}
}
}
}
目前开发PC端或者移动端项目,一般情况下都会选择使用Vue这款JS框架进行开发。饿了么前端团队开发很多强大的、适用的且基于Vue的组件库,无论是PC端还是移动端都有,感兴趣的可以来这里看一看。下面谈一谈我在使用element-ui遇到的问题以及解决方式。
一般分为两种方式,一种是全局引用组件,另一种是按需引用组件
// 使用element-ui框架
import Element from "element-ui";
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Element);
import { Button, Select } from 'element-ui'
Vue.use(Button)
一般情况下,我们在Vue项目中写样式的时候都会习惯加上scoped,以保证各个组件之间的样式不会冲突。然而element-ui的样式我们是在全局引入的,所以你想在某个组件里面覆盖它的样式就不能加scoped,但你又想只覆盖这个页面的element样式,你就可在它的父级加一个class,以用命名空间来解决问题(假设你已经会使用CSS预编译语言)。
.report {
.el-tabs__item {
width: 150px;
text-align: center;
font-size: 20px;
}
}
导致这个问题出现的原因目前还不清楚,只知道只要这么做那么就能解决了
.el-table th.gutter {
display: table-cell !important;
}
很多问题和重点往往会被忽略掉,除非在项目中遇到了。Axios(阿西奥斯)中文文档
get请求向后端发送有两种方式,一种是是在url地址后面进行拼接
axios({
method: 'get',
url: 'http://llxmj.s1.natapp.cc/oauth/platformLogin?phone=' + this.phone + '&code=' + this.code
}).then(function(res) {
if(res.code == 0) {
self.$router.push({
path: '/'
})
}
}).catch(function(error) {
console.log('访问登录提交接口失败')
})
另一种是利用params关键字
axios({
method: 'get',
url: 'http://llxmj.s1.natapp.cc/oauth/platformLogin',
params: {
phone: this.phone,
code: this.code
}
}).then(function(res) {
if(res.code == 0) {
self.$router.push({
path: '/'
})
}
}).catch(function(error) {
console.log('访问登录提交接口失败')
})
post请求向后端发送数据的方式目前只有一种,就是利用data关键字
axios({
method: 'post',
url: 'http://llxmj.s1.natapp.cc/oauth/platformLogin',
data: {
phone: this.phone,
code: this.code
}
}).then(function(res) {
if(res.code == 0) {
self.$router.push({
path: '/'
})
}
}).catch(function(error) {
console.log('访问登录提交接口失败')
})
废话不多说了,直接看代码,相信都能看懂,是否需要用到this,这个还是得看具体情况的。
targetingTagReports() {
let TagReportsOne = {
accountId: this.accountId,
dateRange: this.dateRange,
level: "ADVERTISER",
type: "GENDER"
};
let TagReportsTwo = {
accountId: this.accountId,
dateRange: this.dateRange,
level: "CAMPAIGN",
filtering: [
{
field: "campaign_id",
operator: "EQUALS",
values: ["18844"]
}
],
type: "AGE"
};
let TagReportsThree = {
accountId: this.accountId,
dateRange: this.dateRange,
level: "ADGROUP",
filtering: [
{
field: "adgroup_id",
operator: "EQUALS",
values: ["18844"]
}
],
type: "REGION"
};
this.axios.all([
this.axios({
method: "post",
url: "adData/targetingTagReports",
headers: {
"Content-Type": "application/json"
},
data: TagReportsOne
}),
this.axios({
method: "post",
url: "adData/targetingTagReports",
headers: {
"Content-Type": "application/json"
},
data: TagReportsTwo
}),
this.axios({
method: "post",
url: "adData/targetingTagReports",
headers: {
"Content-Type": "application/json"
},
data: TagReportsThree
})
]).then(this.axios.spread((genderes, ageres, regionres)=>{
console.log(genderes);
console.log(ageres);
console.log(regionres);
}))
}
注意:滚动时需要加额外值与元素距离网页的高度进行判断
<template>
<div class="demo">
<div class="header"></div>
<div class="tab" ref="tabBar" :class="tabBarFixed ? 'tab-fixed' : ''">
<ul>
<li
class="item"
:class="{ active: index == tabIndex }"
v-for="(item, index) in tabTexts"
:key="index"
@click="tabClick(index)"
>
{{ item }}
</li>
</ul>
</div>
<div class="list" v-for="(item, index) in 10" :key="index">
{{ item }}
</div>
</div>
</template>
<script>
export default {
name: "Demo",
data() {
return {
tabTexts: ["导航一", "导航二"],
tabIndex: 0,
tabBarFixed: false, //tab栏固定到顶部
tabOffet: "" //tab距离顶部的高度
};
},
mounted() {
window.addEventListener("scroll", this.handleScroll);
this.tabOffet = this.$refs.tabBar.offsetTop;
},
destroyed() {
window.removeEventListener("scroll", this.handleScroll);
},
methods: {
// tab栏顶部固定
handleScroll() {
let scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop;
// 不加50这个值存在页面滚动发生抖动的bug,原因:当滚动到临界点时,scrollTop值会因为固定定位发生变化
// 50这个值根据具体的场景而变化,不是一个定值
if (scrollTop + 50 > this.tabOffet) {
this.tabBarFixed = true;
} else {
this.tabBarFixed = false;
}
},
tabClick(index) {
this.tabIndex = index;
}
}
};
</script>
<style scoped>
.demo {
padding: 0;
margin: 0;
width: 100vw;
min-height: 100vh;
background-color: #eee;
}
.demo .header {
height: 180px;
width: 100vw;
background-color: bisque;
}
.demo .tab {
width: 100vw;
height: 40px;
line-height: 40px;
padding: 0 10px;
box-sizing: border-box;
background-color: #fff;
border-bottom: 2px solid #d5d5d5;
color: #d5d5d5;
}
.tab ul {
display: flex;
font-size: 24px;
}
.tab ul .item {
flex: 1;
text-align: center;
}
.tab ul .active {
border-bottom: 3px solid #eebb59;
color: #000;
}
.demo .tab-fixed {
position: fixed;
top: 0;
left: 0;
}
</style>
涉及到FileReader对象的原理以及知识请来这里进行食用
<template>
<div class="hello">
<img :src="headImg">
<input type="file" accept="image/*" @change="select_img">
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
headImg: ""
};
},
methods: {
select_img: function(e) {
var vm = this;
this.file = event.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(this.file);
reader.onload = e => {
const src = e.target.result;
vm.headImg = src;
};
}
}
};
</script>
<style scoped></style>
一般来说上传的图片不能过大,否则会使服务器负载较大,所以我们需要获取指定文件的大小来判断
<template>
<div class="hello">
<img :src="headImg">
<input type="file" accept="image/*" @change="select_img">
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
headImg: '',
filename: ''
};
},
methods: {
// 上传图片时触发的操作
select_img: function(e) {
var vm = this;
var img_size = e.target.files[0].size;
this.file = e.target.files[0];
vm.filename = this.file.name;
// 图片预览
const reader = new FileReader();
reader.readAsDataURL(this.file);
reader.onload = (e)=>{
if(img_size >= 29000){
console.log('图片太大了');
//调用自定义方法来处理图片
vm.compressImage(e.target.result);
} else {
const src = e.target.result;
vm.headImg = src;
}
}
},
// 图片过大时的操作
compressImage: function(bdata) {
var self = this;
// 压缩图片的质量
var quality = 0.3;
// 压缩前的图片大小
var oldimglength = bdata.length;
// 压缩率
var compresRadio = 0;
// 创建画布
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.src = bdata;
img.onload = function() {
var width = img.width;
var height = img.height;
// 自定义图片大小
canvas.width = 100;
canvas.height = 100 * (img.height/img.width);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 将图片转为Base64之后预览要用
var cdata = canvas.toDataURL('imgge/jpeg', quality);
// 预览压缩后的图片
self.headImg = cdata;
var newimglength = cdata.length;
// 压缩前后图片大小对比
console.log('old-img-size:'+oldimglength)
console.log('new-img-size:'+newimglength)
compresRadio = (((oldimglength-newimglength)/oldimglength*100).toFixed(2))+'%';
console.log('压缩率为:'+compresRadio)
}
}
}
};
</script>
<style scoped>
</style>
以上涉及到的知识点有Image对象和canvas对象中的toDataURL方法
以上的代码得到是上传图片base64格式的地址,如果后台接口需要指定一个file类型文件,那么我们需要做如下的转换
<template>
<div class="hello">
<img :src="headImg">
<input type="file" accept="image/*" @change="select_img">
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
headImg: '',
filename: ''
};
},
methods: {
// 上传图片时触发的操作
select_img: function(e) {
var vm = this;
var img_size = e.target.files[0].size;
this.file = e.target.files[0];
vm.filename = this.file.name;
// 图片预览
const reader = new FileReader();
reader.readAsDataURL(this.file);
reader.onload = (e)=>{
if(img_size >= 29000){
console.log('图片太大了');
//调用自定义方法来处理图片
vm.compressImage(e.target.result);
} else {
const src = e.target.result;
vm.headImg = src;
}
}
},
// 图片过大时的操作
compressImage: function(bdata) {
var self = this;
// 压缩图片的质量
var quality = 0.3;
// 压缩前的图片大小
var oldimglength = bdata.length;
// 压缩率
var compresRadio = 0;
// 创建画布
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.src = bdata;
img.onload = function() {
var width = img.width;
var height = img.height;
// 自定义图片大小
canvas.width = 100;
canvas.height = 100 * (img.height/img.width);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 将图片转为Base64之后预览要用
var cdata = canvas.toDataURL('imgge/jpeg', quality);
// 进行base64转成file类型处理
var arr = cdata.split(',');
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while(n--) {
u8arr[n] = bstr.charCodeAt(n);
}
self.newFile = new File([u8arr], self.filename, {type:mime})
// 转换为file类型
console.log(self.newFile);
// 预览压缩后的图片
self.headImg = cdata;
var newimglength = cdata.length;
// 压缩前后图片大小对比
console.log('old-img-size:'+oldimglength)
console.log('new-img-size:'+newimglength)
compresRadio = (((oldimglength-newimglength)/oldimglength*100).toFixed(2))+'%';
console.log('压缩率为:'+compresRadio)
}
}
}
};
</script>
<style scoped>
</style>
路由是根据不同的url地址展示不同的内容或者页面。前端路由就是把不同路由对应不同的内容或者页面的任务交给前端来做,之前是通过服务端根据url的不同返回不同的页面实现的。
在单页面应用,大部分页面结构不变,只改变部分内容的使用。
其实就是对JavaScript总history对象的一个封装。
动态路由的含义就是url地址中带有参数,这些参数可以自己手动在url上面添加,也可以通过js动态赋值,下面我们在看一下手动在url上面添加的实现
// 路由配置
import Vue from 'vue'
import Router from 'vue-router'
import TestApp from '@/components/TestApp'
import GoodsList from '@/views/GoodsList'
Vue.use(Router)
export default new Router({
routes: [{
path: '/test/:testId/user/:name',
name: 'TestApp',
component: TestApp
}, {
path: '/goods/:goodsId',
name: 'GoodsList',
component: GoodsList
}]
})
// GoodsList组件
<template>
<div class="goods">
<h3>这是商品列表</h3>
<p>获取到的商品id为:{{$route.params.goodsId}}</p>
</div>
</template>
<script>
</script>
<style>
</style>
// TestApp组件
<template>
<div id="app">
<h3>这是测试列表</h3>
<p>获取的id是:{{$route.params.testId}}</p>
<p>获取的名字是:{{$route.params.name}}</p>
</div>
</template>
<script>
</script>
<style>
</style>
// 访问GoodsList组件示例:http://localhost:8080/#/goods/123
// 访问TestApp组件示例:http://localhost:8080/#/test/789/user/jack
这是因为路由地址跳转默认(mode)是以哈希方式(hash),可以修改为history方式,这样就无需加个#符号了
import Vue from 'vue'
import Router from 'vue-router'
import TestApp from '@/components/TestApp'
import GoodsList from '@/views/GoodsList'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [{
path: '/test/:testId/user/:name',
name: 'TestApp',
component: TestApp
}, {
path: '/goods/:goodsId',
name: 'GoodsList',
component: GoodsList
}]
})
这个概念挺好理解的,就在路由地址跳转的地方再增加一层子路由跳转,下面请看代码
// 路由设置
import Vue from 'vue'
import Router from 'vue-router'
import GoodsList from '@/views/GoodsList'
import Title from '@/views/Title'
import Image from '@/views/Image'
Vue.use(Router)
export default new Router({
routes: [{
path: '/goods',
name: 'GoodsList',
component: GoodsList,
children: [{
path: 'title',
name: 'Title',
component: Title
}, {
path: 'img',
name: 'Image',
component: Image
}]
}]
})
// GoodsList父组件
<template>
<div class="goods">
<h3>这是商品列表</h3>
<router-link to="/goods/title">显示商品标题</router-link>
<router-link to="/goods/img">显示商品图片</router-link>
<router-view></router-view>
</div>
</template>
<script>
</script>
<style>
</style>
// 其中两个子组件随意加点内容进行区分就好了,在这就不贴代码~
通过JavaScript来实现路由跳转,这种方式在正式的工作中常用,下面请看详细代码
// 路由配置文件
import Vue from 'vue'
import Router from 'vue-router'
import GoodsList from '@/views/GoodsList'
import Cart from '@/views/Cart'
Vue.use(Router)
export default new Router({
routes: [{
path: '/goods',
name: 'GoodsList',
component: GoodsList
}, {
path: '/cart1',
name: 'Cart',
component: Cart
}]
})
// GoodsList组件
<template>
<div class="goods">
<h3>这是商品列表</h3>
<button @click="jump1">第一种跳转到购物车方式</button>
<button @click="jump2">第二种跳转到购物车方式</button>
<button @click="jump3">第三种跳转到购物车方式</button>
<button @click="jump4">第四种跳转到之前的页面</button>
</div>
</template>
<script>
export default {
data() {
return {}
},
methods: {
jump1() {
this.$router.push('/cart1')
},
jump2() {
this.$router.push({
path: '/cart1'
})
},
jump3() {
this.$router.push({
path: '/cart1?goodsId=123'
})
},
jump4() {
// 后退两步
this.$router.go(-2)
}
}
}
</script>
<style>
</style>
当然编程式路由还可以这么来用
this.$router.push({
name: 'ToolCaseList',
params: {
toolcaseId: 'asetting'
}
})
// 配置路由,注意这里不能使用:/toolcaseId来传递参数了,
// 因为父组件中,已经使用params来携带参数了
{
path: "/toolcaselist",
name: "ToolCaseList",
component: ToolCaseList
}
子组件中: 这样来获取参数$route.params.toolcaseId
当然,我们还可以设置跳转打开新窗口的方式。比如,使用路由对象的resolve方法解析路由,可以得到location、router、href等目标路由的信息。得到href就可以使用window.open开新窗口了。
adverTising(id, name, status) {
const { href } = this.$router.resolve({
name: "LauHome",
params: {
accountId: id,
accountName: name,
systemStatus: status
}
});
window.open(href, '_blank')
},
// 路由配置文件
import Vue from 'vue'
import Router from 'vue-router'
import GoodsList from '@/views/GoodsList'
import Cart from '@/views/Cart'
Vue.use(Router)
export default new Router({
routes: [{
path: '/goods',
name: 'GoodsList',
component: GoodsList
}, {
path: '/cart',
name: 'Cart',
component: Cart
}, {
path: '/cart/:cartId',
name: 'Cart1',
component: Cart
}]
})
// GoodsList组件
<template>
<div class="goods">
<h3>这是商品列表</h3>
<router-link :to="{name:'Cart'}">不传参数跳转到购物车</router-link>
<router-link :to="{name:'Cart1',params:{cartId:123}}">传递参数跳转到购物车</router-link>
</div>
</template>
<script>
</script>
<style>
</style>
// Cart组件
<template>
<div class="cart">
<h3>这是购物车页面</h3>
<p>获取传入过来的参数:{{$route.params.cartId}}</p>
</div>
</template>
<script>
</script>
<style>
</style>
// App.vue根组件
<template>
<div id="app">
<router-view></router-view>
<router-view name='title'></router-view>
<router-view name='img'></router-view>
</div>
</template>
<script>
</script>
<style>
</style>
// 路由配置文件
import Vue from 'vue'
import Router from 'vue-router'
import GoodsList from '@/views/GoodsList'
import Title from '@/views/Title'
import Image from '@/views/Image'
import Cart from '@/views/Cart'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'GoodsList',
components: {
default: GoodsList,
title: Title,
img: Image
}
}, {
path: '/cart',
name: 'Cart',
component: Cart
}]
})
这个很容易理解,就是当访问某个地址的时候,这个地址又会自动跳转到另一个地址,这就是路由重定向的大白话解释,下面直接看代码就明白是怎么回事了。
import Vue from 'vue'
import Router from 'vue-router'
import Ebook from '@/Ebook'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/ebook'
},
{
path: '/ebook',
name: 'Ebook',
component: Ebook
}
]
})
首先说一下场景:点击父组件某个按钮,跳转到子组件中,并且把对应按钮的id给传递过去,子组件得到该id,然后根据id值的不同来做对应的操作。
下面请看方式一实现:
// 父组件
<ul>
<li v-for="item in list" @click="getData(item.id)"></li>
</ul>
getData(id) {
this.$router.push({
path: `/children/${id}`
})
}
// 路由配置文件
{
path: '/chlidren/:id',
name: 'Children',
component: Children
}
// 子组件
this.$route.params.id
方式二:
// 父组件
<ul>
<li v-for="item in list" @click="getData(item.id)"></li>
</ul>
getData(id) {
this.$router.push({
name: 'Children',
params: {
id: id
}
})
}
// 路由配置文件
{
path: '/chlidren',
name: 'Children',
component: Children
}
// 子组件
this.$route.params.id
方式三:
// 父组件
<ul>
<li v-for="item in list" @click="getData(item.id)"></li>
</ul>
getData(id) {
this.$router.push({
path: '/children',
query: {
id: id
}
})
}
// 路由配置文件
{
path: '/chlidren',
name: 'Children',
component: Children
}
// 子组件
this.$route.query.id
注意: query传参要用path来引入,params传参要用name来引入。
一般情况,后台接口返回的数据前端需要做二次处理,比如“金额”,后台返回是以“分”为单位,前端需要展示以“元”为单位,并保留两位数。
这种需求不建议直接在页面上写<h2>{{ (price/100).toFixed(2) }}</h2>
,我们需要创建一个严谨的全局过滤器,能够在项目中任何一个地方进行调用。
src/lib/filters.js
function formatMoney(value, fractionDigits = 2) {
if (!value) {
return "";
}
return (value / 100).toFixed(fractionDigits).toString();
}
export default function(instance) {
instance.filter("formatMoney", formatMoney);
}
src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import initFilters from "@/lib/filters"
Vue.config.productionTip = false
initFilters(Vue)
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
template文件调用
<template>
<div class="demo">
<h1>{{ msg }}</h1>
<h2>{{ price | formatMoney }}</h2>
<h2>{{ (price/100).toFixed(2) }}</h2>
</div>
</template>
<script>
export default {
name: 'Demo',
data () {
return {
msg: 'Welcome to Your Vue.js App',
price: 1000
}
}
}
</script>
<style scoped></style>
import Vue from 'vue'
import App from './App'
import router from './router'
import 'mint-ui/lib/style.css'
import { InfiniteScroll, Spinner } from 'mint-ui';
Vue.component(Spinner.name, Spinner);
Vue.use(InfiniteScroll);
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<title>Demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but s3_recharge doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
<script>
//屏幕适配
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var html = document.documentElement;
var k = 740;
html.style.fontSize = html.clientWidth / k * 100 + "px";
}
setFont();
setTimeout(function () {
setFont();
}, 300);
doc.addEventListener('DOMContentLoaded', setFont, false);
win.addEventListener('resize', setFont, false);
win.addEventListener('load', setFont, false);
document.documentElement.addEventListener('touchstart', function (event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, false);
var lastTouchEnd = 0;
document.documentElement.addEventListener('touchend', function (event) {
var now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 解决ios safari无法禁止双指缩放问题
document.addEventListener('gesturestart', function (event) {
event.preventDefault();
});
})(window, document);
</script>
</html>
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family: 'Microsoft YaHei UI';
box-sizing: border-box;
position: relative;
z-index: -1;
}
h1,
h2,
h3,
h4,
h5,
p,
ul,
li,
button,
a {
padding: 0;
margin: 0;
font-weight: normal;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
button {
border: none;
outline: none
}
/* 金黄色 */
.golden {
color: #ec9c00;
}
/* 灰色背景色 */
.graybgc {
background-color: #eee;
}
/* 深灰色字体色 */
.graytext {
color: #717171;
}
span {
display: inline-block;
vertical-align: middle;
}
编写结构代码
<template>
<div class="order-list graybgc">
<div class="order-wrapper">
<div
class="order-info"
ref="itemwrapper"
v-show="dealList && dealList.length > 0"
v-infinite-scroll="loadMore"
infinite-scroll-disabled="loading"
infinite-scroll-distance="50"
>
<div>
<div class="order-box" v-for="(item, index) in dealList" :key="index">
<div class="header">
<div class="user-info">
<span class="game-name">火影忍者</span>
<span class="game-serve">
张三丰-微信17区
</span>
</div>
<div class="order-status">
<span class="success golden">支付成功</span>
</div>
</div>
<div class="info">
<div class="info-img">
<img
src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583325310315&di=8d5d1b8c9a22543bfc5c8fad58ff79cf&imgtype=0&src=http%3A%2F%2Fimages.china.cn%2Fattachement%2Fjpg%2Fsite1000%2F20141203%2F7427ea21095115e896280f.jpg"
alt=""
/>
</div>
<div class="info-number">
<div class="title">
新春礼包
</div>
<div class="money">
单价:¥10/ 件
</div>
<div class="number">数量:20 件</div>
</div>
</div>
<div class="footer">
<div class="time graytext">
<span class="date">02-10 22:00</span>
</div>
<div class="total-money">
<span class="text graytext">合计:</span>
<div class="money">
<span class="min">¥</span>
<span>200</span>
</div>
</div>
</div>
</div>
<p v-show="loading" class="bottom-tip">
<mt-spinner color="#26a2ff"></mt-spinner>
</p>
<div class="null-box"></div>
</div>
</div>
<div class="order-no" v-if="!dealList || dealList.length == 0">
<div class="img"></div>
<div class="desc graytext">暂时还没您的购买记录哦</div>
</div>
</div>
<div class="order-footer graytext">
当前页面仅提供30天记录查询,如有疑问,请
<a href="#">联系客服</a>
</div>
</div>
</template>
编写样式代码
<style scoped lang="less">
.order-list {
position: relative;
width: 100%;
min-height: 100%;
padding: 0.25rem 0.4rem 0;
box-sizing: border-box;
.order-wrapper {
.order-info {
width: 100%;
height: 90vh;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
.top-tip {
width: 100%;
height: 0.6rem;
line-height: 0.1rem;
text-align: center;
}
.order-box {
background-color: #fff;
padding: 0.15rem 0.3rem;
box-sizing: border-box;
border-radius: 0.05rem;
font-size: 0.2rem;
margin-bottom: 0.3rem;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.user-info {
color: #000;
text-align: left;
.game-name {
margin-right: 0.1rem;
}
}
.order-status {
.no-play-btn {
padding: 0.01rem 0.2rem;
background-color: #ec6f00;
color: #fff;
border-radius: 0.2rem;
margin-left: 0.1rem;
}
}
}
.info {
display: flex;
width: 100%;
padding: 0.1rem 0 0.15rem;
box-sizing: border-box;
border-bottom: 0.02rem solid #d5d5d5;
.info-img {
display: inline-block;
vertical-align: top;
width: 1.1rem;
height: 1.1rem;
background-color: #ec6f00;
text-align: center;
line-height: 1.1rem;
margin-right: 0.2rem;
position: relative;
img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-block;
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
}
}
.info-number {
display: inline-block;
vertical-align: top;
height: 1.1rem;
text-align: left;
padding: 0;
margin: 0;
.title {
font-size: 0.28rem;
color: #000;
}
}
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 0.15rem;
.money {
display: inline-block;
vertical-align: middle;
font-size: 0.28rem;
color: #000;
span {
vertical-align: baseline;
}
.min {
font-size: 0.22rem;
}
}
}
}
.null-box {
width: 100%;
height: 1px;
}
.bottom-tip {
text-align: center;
margin-top: -0.2rem;
height: 1rem;
line-height: 1rem;
}
}
.order-no {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) !important;
width: 4rem;
height: 5rem;
text-align: center;
.img {
width: 1.38rem;
height: 1.8rem;
background-color: #eee;
margin: 0 auto;
}
.desc {
margin-top: 0.4rem;
font-size: 0.3rem;
}
}
}
.order-footer {
width: 100%;
height: 0.6rem;
line-height: 0.6rem;
position: fixed;
bottom: 0;
left: 0;
font-size: 0.24rem;
background-color: inherit;
z-index: 1;
text-align: center;
a {
text-decoration: underline;
color: #000;
}
}
}
</style>
编写js代码
<script>
export default {
name: "OrderList",
data() {
return {
pageIndex: 1,
dealList: [], //订单记录列表
loading: false,
allLoaded: false,
lock: false //避免同一时间多次请求数据
};
},
created() {
this.getOrderList();
},
methods: {
loadMore() {
if (this.allLoaded) {
return;
}
this.loading = true;
setTimeout(() => {
this.pageIndex++;
this.getOrderList();
}, 1500);
},
// 获取订单列表数据
getOrderList(status) {
let data = [1, 2, 3]; //模拟接口返回的数据
this.loading = false;
if (data && data.length > 0) {
if (this.pageIndex > 1) {
this.dealList = this.dealList.concat(data);
} else {
this.dealList = data;
}
} else {
//没有数据
this.allLoaded = true;
}
}
}
};
</script>
涉及到的依赖总共有三个,首先得安装这三个依赖,分别是
接着我们在单个组件中引入
import FileSaver from "file-saver";
import XLSX from "xlsx";
下面来看看html结构的代码
<template>
<div class="financo-footer" ref="table">
<el-table
:data="fincorTableDates"
max-height="450"
border
v-loading="tencentF"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
style="width: 100%"
>
<el-table-column prop="dateTime" sortable label="日期" align="center">
</el-table-column>
<el-table-column prop="tradeType" label="操作类型" align="center">
</el-table-column>
<el-table-column label="金额(元)" align="center">
<template slot-scope="scope">
{{scope.row.amount/100}}
</template>
</el-table-column>
</el-table>
</div>
</template>
最后就是方法处理代码
exportExcel() {
let tableDom = this.$refs.table;
var wb = XLSX.utils.table_to_book(tableDom);
var wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array"
});
try {
FileSaver.saveAs(
new Blob([wbout], {
type: "application/octet-stream"
}),
"下载文件名.xlsx"
);
} catch (e) {
if (typeof console !== "undefined") {
return e;
}
}
return wbout;
}
前端下载表格会带来一些问题,假如固定了某一列,那么下载表格的时候就会下载出重复的数据,解决暂时就是不要设置固定列。
后台下载表格数据,前端只要传递相关的参数即可,不过要注意的是,不能使用异步的方法去传递参数,比如下面的方式是错误的
downloadExcel() {
let adDataModelObj = {
dateRange: this.dateRange,
accountId: this.accountId,
level: this.level
};
this.axios({
method: "post",
url: "http://????/excel/dateReports",
headers: {
"Content-Type": "application/json"
},
data: {
adDataModel: adDataModelObj
}
}).then((res) => {
console.log(res)
});
}
正确的方式应该使用地址跳转如下:
downloadExcel() {
window.location.href =
"http://?????/excel/HomePageReport?endTime=" +
this.dateRange.endDate +
"&startTime=" +
this.dateRange.startDate +
"&accountId=" +
this.accountId;
}
代码如下
<template>
<div>
<span v-if="!flag" style="color: #FF6000">倒计时{{ time }}</span>
</div>
</template>
<script>
export default {
name: 'countDown',
props: {
endTime: {
type: String,
default: () => {
return '';
},
},
},
data() {
return {
time: '',
flag: false,
};
},
mounted() {
let time = setInterval(() => {
if (this.flag == true) {
clearInterval(time);
}
this.timeDown();
}, 500);
},
methods: {
timeDown() {
const endTime = new Date(this.endTime);
const nowTime = new Date();
let leftTime = parseInt((endTime.getTime() - nowTime.getTime()) / 1000);
let d = parseInt(leftTime / (24 * 60 * 60));
let h = this.formate(parseInt((leftTime / (60 * 60)) % 24));
let m = this.formate(parseInt((leftTime / 60) % 60));
let s = this.formate(parseInt(leftTime % 60));
if (leftTime <= 0) {
this.flag = true;
this.$emit('timeEnd');
}
// this.time = `${d}天${h}小时${m}分${s}秒`;
this.time = `${m}:${s}`;
},
formate(time) {
if (time >= 10) {
return time;
} else {
return `0${time}`;
}
},
},
};
</script>
<style scoped lang="less"></style>
import Vue from 'vue'
import App from './App'
import router from './router'
import vueSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.config.productionTip = false
Vue.use(vueSwiper)
new Vue({
el: '#app',
router,
components: {
App
},
template: '<App/>'
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<title>Demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but s3_recharge doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
<script>
//屏幕适配
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var html = document.documentElement;
var k = 740;
html.style.fontSize = html.clientWidth / k * 100 + "px";
}
setFont();
setTimeout(function () {
setFont();
}, 300);
doc.addEventListener('DOMContentLoaded', setFont, false);
win.addEventListener('resize', setFont, false);
win.addEventListener('load', setFont, false);
document.documentElement.addEventListener('touchstart', function (event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, false);
var lastTouchEnd = 0;
document.documentElement.addEventListener('touchend', function (event) {
var now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 解决ios safari无法禁止双指缩放问题
document.addEventListener('gesturestart', function (event) {
event.preventDefault();
});
})(window, document);
</script>
</html>
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family: 'Microsoft YaHei UI';
box-sizing: border-box;
position: relative;
z-index: -1;
}
h1,
h2,
h3,
h4,
h5,
p,
ul,
li,
button,
a {
padding: 0;
margin: 0;
font-weight: normal;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
button {
border: none;
outline: none
}
编写代码
<template>
<div class="box">
<div>
<swiper :options="swiperOption">
<swiper-slide
class="swiper-slide"
v-for="(item, index) in slide"
:key="index"
>
<!-- 我是第{{item}}个轮播图 -->
<img
src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583386359408&di=fac882573a148a13fe7a7b14f8a014c1&imgtype=0&src=http%3A%2F%2Fa4.att.hudong.com%2F21%2F09%2F01200000026352136359091694357.jpg"
alt=""
/>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
<!-- 分页 -->
<div class="swiper-button-prev" slot="button-prev"></div>
<!-- 箭头左 -->
<div class="swiper-button-next" slot="button-next"></div>
<!-- 箭头右 -->
</swiper>
</div>
</div>
</template>
<script>
export default {
name: "Demo",
data() {
return {
slide: [1, 2, 3, 4, 5],
//设置属性
swiperOption: {
//显示分页
pagination: {
el: ".swiper-pagination",
clickable: true //允许分页点击跳转
},
//设置点击箭头
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev"
},
//自动轮播
autoplay: {
delay: 1000
},
//开启循环模式
loop: true,
//开启鼠标滚轮控制Swiper切换
mousewheel: true
}
};
}
};
</script>
<style>
img {
width: 100%;
height: 5rem;
background-size: 100% 100%;
}
.swiper-slide {
width: 100%;
height: 5rem;
line-height: 5rem;
font-size: 0.3rem;
text-align: center;
background-color: rosybrown;
}
.swiper-button-prev,
.swiper-button-next {
height: 0.4rem;
width: 0.55rem;
}
.swiper-button-prev {
left: 0.3rem;
/* background-image: url(""); 可以修改切换的图标*/
}
.swiper-button-next {
right: 0.3rem;
/* background-image: url(""); 可以修改切换的图标*/
}
/* 修改底部小圆点的位置 */
.swiper-pagination-fraction,
.swiper-pagination-custom,
.swiper-container-horizontal > .swiper-pagination-bullets {
height: 0.5rem;
line-height: 0.4rem;
bottom: 0.1rem;
}
/* 修改小圆点未激活样式 */
.swiper-pagination-bullet {
background-color: green;
}
/* 修改小圆点激活样式 */
.swiper-pagination-bullet-active {
background-color: yellow;
}
</style>
当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。
根据上面的概念,我们可以知道,如果写了如下的代码,页面必定会卡死,程序陷入无限循环之中
beforeUpdate() {
this.$nextTick(() => {
this.getFoptions()
})
}
methods: {
getFoptions() {
let status = this.firstValue
if(status == 'ecommerce') {
this.showCode = true
let arr = [{
value: 'a',
label: '综合电商'
}, {
value: 'b',
label: '返利平台'
}, {
value: 'c',
label: '电商导购'
}]
for(var i = 0; i < arr.length; i++) {
console.log(arr[i].value)
this.soptions.push({
value: arr[i].value,
label: arr[i].label
})
}
}
if(status == 'join') {
this.showCode = true
}
}
}
}
那么如何来改变这一个线状呢?其实很简单,利用watch即可
watch: {
firstValue() {
this.getFoptions()
}
},
methods: {
getFoptions() {
let status = this.firstValue
if(status == 'ecommerce') {
this.showCode = true
let arr = [{
value: 'a',
label: '综合电商'
}, {
value: 'b',
label: '返利平台'
}, {
value: 'c',
label: '电商导购'
}]
for(var i = 0; i < arr.length; i++) {
console.log(arr[i].value)
this.soptions.push({
value: arr[i].value,
label: arr[i].label
})
}
}
if(status == 'join') {
this.showCode = true
}
}
}
}
对echarts一直处于懵懵懂懂的状态,导致以为能调用API就代表是会了,然而随后的工作给了我一个响亮的巴掌,因此在这里做一个详细总结。
一般有两种使用方式,一种是将echarts的全部组件引入使用,这样会导致最后打包的项目过大,影响到了一定的应用程序性能;另一种是将echarts按需引入,一般情况下,这是首选,同时也考察了对echarts的熟悉度。下面来看一下全局引入echarts的写法
步骤一:全局引入
// main.js
import echarts from 'echarts'
Vue.prototype.$echarts = echarts
步骤二:在vue文件中使用
<div id="myChart"></div>
<script>
export default {
data() {
return {
msg: 'echarts'
}
},
mounted() {
this.drawLine();
},
methods: {
drawLine() {
// 基于准备好的dom,初始化echarts实例
let myChart = this.$echarts.init(document.getElementById('myChart'))
// 绘制图表
var option = {
title: {
text: '在Vue中使用echarts'
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
}
myChart.setOption(option);
}
}
}
</script>
另一种方式就是按需引入echarts组件,这种方式不用在main.js文件中为Vue对象的原型添加属性$echarts,因此调用的方式自然而然发生了改变,下面也给出代码栗子
<div id="myChart"></div>
<script>
// 引入基本模板
let echarts = require('echarts/lib/echarts')
// 引入柱状图组件
require('echarts/lib/chart/bar')
// 引入提示框和title组件
require('echarts/lib/component/tooltip')
require('echarts/lib/component/title')
export default {
data() {
return {
msg: 'echarts'
}
},
mounted() {
this.drawLine();
},
methods: {
drawLine() {
// 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.getElementById('myChart'))
// 绘制图表
var option = {
title: {
text: '在Vue中使用echarts'
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
}
myChart.setOption(option);
}
}
}
</script>
上述代码之所以使用 require引入echarts组件 而不是 import,是因为 require 可以直接从 node_modules 中查找,而 import 必须把路径写全。
安装好依赖后,我们这样写
// 引入echarts基本模板
let echarts = require("echarts/lib/echarts");
// 引入地图组件
require("echarts/lib/chart/map");
//引入主题文件
require("echarts/theme/macarons.js");
// 引入**地图组件
require("echarts/map/js/china.js");
// 引入echarts中的funnel组件
require("echarts/lib/chart/funnel");
// 引入echarts提示框组件
require("echarts/lib/component/tooltip");
// 引入echarts图例组件
require("echarts/lib/component/legend");
draw() {
// 获取到这个DOM节点,然后初始化,并且设置主题色
var repChart = echarts.init(document.getElementById("address"), "macarons");
var reoption = {
tooltip: {
trigger: "item"
},
legend: {
x: "left",
selectedMode: false,
data: ["北京", "上海", "广东"]
},
dataRange: {
orient: "vertical",
min: 0,
max: 100000,
text: ["高", "低"], // 文本,默认为数值文本
splitNumber: 0
},
toolbox: {
show: true,
orient: "vertical",
x: "left",
y: 20,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false }
}
},
series: [{
name: "全国曝光量分布",
type: "map",
mapType: "china",
mapLocation: {
x: "left"
},
selectedMode: "multiple",
itemStyle: {
normal: { label: { show: true } },
emphasis: { label: { show: true } }
},
data:[
{name:'西藏', value:605.83},
{name:'青海', value:1670.44},
{name:'宁夏', value:2102.21},
{name:'海南', value:2522.66},
{name:'甘肃', value:5020.37},
{name:'贵州', value:5701.84},
{name:'**', value:6610.05},
{name:'云南', value:8893.12},
{name:'重庆', value:10011.37},
{name:'吉林', value:10568.83},
{name:'山西', value:11237.55},
{name:'天津', value:11307.28},
{name:'江西', value:11702.82},
{name:'广西', value:11720.87},
{name:'陕西', value:12512.3},
{name:'黑龙江', value:12582},
{name:'内蒙古', value:14359.88},
{name:'安徽', value:15300.65},
{name:'北京', value:16251.93, selected:true},
{name:'福建', value:17560.18},
{name:'上海', value:19195.69, selected:true},
{name:'湖北', value:19632.26},
{name:'湖南', value:19669.56},
{name:'四川', value:21026.68},
{name:'辽宁', value:22226.7},
{name:'河北', value:24515.76},
{name:'河南', value:26931.03},
{name:'浙江', value:32318.85},
{name:'山东', value:45361.85},
{name:'江苏', value:49110.27},
{name:'广东', value:53210.28, selected:true}
]
// data: this.proSeriesData // 动态渲染数据
}]
};
repChart.setOption(reoption);
},
根据上面的栗子我们来看看各个配置项的作用是什么
var option = {
// 设置echarts整个区域的背景色
backgroundColor: '#000'
}
var option = {
// 设置echarts整个区域的背景色
backgroundColor: '#000',
// 图表标题
title: {
// 主标题名称,和副标题在一个区域内
text: '父温度表',
// 副标题名称,和主标题在一个区域内',
subtext: '子温度表',
// 标题x轴位置,默认为左对齐,可选为:'center' ¦ 'left' ¦ 'right' | '数值'
x: '150',
// 标题y轴位置,默认为顶对齐,可选为:'top' ¦ 'bottom' ¦ 'center' | '数值'
y: 'top',
// 标题区域文字水平对齐方式
textAlign: 'center',
// 标题区域背景颜色
backgroundColor: '#fff',
// 标题区域边框颜色
borderColor: '#000',
// 标题边框线宽,默认为0(无边框)
borderWidth: 1,
// 标题内边距,默认各方向内边距为5,接受数组分别设定上右下左边距,同css
padding: [5, 10],
// 主副标题纵向间隔,单位px,默认为10,
itemGap: 20,
// 主标题文字样式设置
textStyle: {
fontSize: 20,
fontWeight: 'bolder',
color: 'blue'
},
// 副标题文字样式设置
subtextStyle: {
fontSize: 16,
color: '#aaa'
}
}
}
var option = {
// 图例名
legend: {
// 需要和series配置项中name对应
data: ['曝光量', '点击量'],
// 布局方式,默认为水平布局,可选为:'horizontal' ¦ 'vertical'
orient: 'vertical',
// 水平安放位置,默认为全图居中,可选为:'center' ¦ 'left' ¦ 'right' | '数值'
x: 'center',
// 垂直安放位置,默认为全图顶端,可选为:'top' ¦ 'bottom' ¦ 'center' | 'number'
y: '10',
// 图例区域背景颜色
backgroundColor: '#ccc',
// 图例边框颜色
borderColor: '#000',
// 图例边框线宽,单位px,默认为0(无边框)
borderWidth: 1,
// 图例内边距,单位px,默认各方向内边距为5,接受数组分别设定上右下左边距,同css
padding: [5, 10],
// 各个item之间横纵的间隔,单位px,默认为10,横向布局时为水平间隔,纵向布局时为纵向间隔
itemGap: 10,
// 图例图形宽度,就仅仅是那个图形的宽度
itemWidth: 20,
// 图例图形高度,就仅仅是那个图形的高度
itemHeight: 20,
// 图例文字样式设置
textStyle: {
fontSize: 16,
color: '#fff' // 图例文字颜色
}
}
}
这个配置项的作用到底是啥我目前也不是很清楚,往后遇到了会过来解释一波
var option = {
// 值域
dataRange: {
// 布局方式,默认为垂直布局,可选为:'horizontal' ¦ 'vertical'
orient: 'horizontal',
// 水平安放位置,默认为全图左对齐,可选为:'center' ¦ 'left' ¦ 'right' | 'number'
x: 'left',
// 垂直安放位置,默认为全图底部,可选为:'top' ¦ 'bottom' ¦ 'center' | '数值'
y: '20',
// 值域背景色设置
backgroundColor: '#fff',
// 值域边框颜色
borderColor: '#ccc',
// 值域边框线宽,单位px,默认为0(无边框)
borderWidth: 1,
// 值域内边距,单位px,默认各方向内边距为5,接受数组分别设定上右下左边距,同css
padding: 5,
// 各个item之间的间隔,单位px,默认为10,横向布局时为水平间隔,纵向布局时为纵向间隔
itemGap: 10,
// 值域图形宽度,线性渐变水平布局宽度为该值 * 10
itemWidth: 25,
// 值域图形高度,线性渐变垂直布局高度为该值 * 10
itemHeight: 15,
// 分割段数,默认为5,意思就是分为5个范围区域
splitNumber: 5,
// 渐变颜色值,一般是浅颜色变深
color: ['#1e90ff', '#f0ffff'],
// 文本,默认为数值文本。设置了这么,那么值域的显示方式将会改变
text: ['高', '低'],
// 值域文字样式设置
textStyle: {
color: '#333'
}
}
}
var option = {
// 工具箱
toolbox: {
// 设置工具箱是否显示
show: true,
// 工具栏icon的布局朝向,默认是'horizontal',可选'vertical'
orient: 'horizontal',
// 工具栏icon的大小,默认是15
itemSize: 20,
// 工具栏icon每项之间的间隔。默认是10,横向布局时为水平间隔,纵向布局时为纵向间隔
itemGap: 15,
// 是否在鼠标 hover的时候显示每个工具icon的标题,默认是true
showTitle: true,
feature: {
// 保存图片,内含更多配置请看官网
saveAsImage: {
// 保存的图片格式,支持'png'和'jpeg'
type: 'png',
// 保存的文件名称
name: 'nice',
backgroundColor: '#000'
},
// 配置还原,内含更多配置请看官网
restore: {},
// 数据视图工具,可以展现当前图表所用的数据,编辑后可以动态更新,内含更多配置请看官网
dataView: {},
// 数据区域缩放,目前只支持直角坐标系的缩放,内含更多配置请看官网
dataZoom: {},
// 动态类型切换,也就是切换图表显示的类型,内含更多配置请看官网
magicType: {
type: ['line', 'bar', 'stack', 'tiled']
},
// 选框组件的控制按钮,内含更多配置请看官网
brush: {},
// 自定义工具栏,命名只能以my开头
myTool: {
show: true,
title: '自定义',
icon: 'image://http://echarts.baidu.com/images/favicon.png',
onclick: function() {
alert('hello echarts')
}
}
}
}
}
var option = {
// 网格,是显示数据的地方,与整个echarts区域不同
grid: {
// 控制网格配置是否显示,默认为true
show: true,
// 距离左边的位置
left: 50,
// 距离顶部的位置
top: 30,
// 距离右边的位置
right: 50,
// 距离底部的位置
bottom: 50,
// 网格的宽度,默认自适应
width: 1200,
// 网格的高度,默认自适应
height: 300,
// 网格区域是否包含坐标轴的刻度标签,默认为false
containLabel: false,
// 网格背景色,默认透明。
backgroundColor: '#fff',
// 网格的边框线宽
borderWidth: 1,
// 网格的边框颜色
borderColor: '#000',
// 图形阴影的模糊大小。该属性配合 shadowColor,shadowOffsetX, shadowOffsetY 一起设置图形的阴影效果。
shadowBlur: 10,
// 阴影颜色
shadowColor: 'red',
// 阴影水平方向上的偏移距离
shadowOffsetX: 2,
// 阴影垂直方向上的偏移距离
shadowOffsetY: 3
}
}
这个配置项是比较重要的,而且内容也是非常多,为了节约精力和时间,只把一些常用的在这标出来,剩下的请自行前往官方网站查阅。
作用:配置x坐标轴刻度、标签、内容、刻度线和鼠标滑入出现的提示信息等。
var option = {
//x轴信息
xAxis: {
show: true,
// x 轴所在的 grid 的索引,默认位于第一个 grid
gridIndex: 0,
// x 轴的位置,可选'top'和'bottom'
position: 'bottom',
// x轴数据向y轴的方向进行偏移
offset: 10,
// 坐标轴类型。默认是category,可选'value'、'category'、'time'、'log'
type: 'category',
name: '时间',
// x轴名称显示的位置,可选'start' | 'middle' | 'center' | 'end'
nameLocation: 'start',
// 坐标轴名称的文字样式
nameTextStyle: {
color: 'red',
fontWeight: 'bolder',
fontSize: 14
},
// x轴名称与轴线之间的距离
nameGap: 15,
// x轴名称旋转,角度值
nameRotate: 5,
// 坐标轴两边留白策略,类目轴和非类目轴的设置和表现不一样
boundaryGap: false,
// x轴数据展示
data: ['12-01', '12-02', {
value: '12-03',
// 突出这个字段
textStyle: {
fontSize: 15,
color: '#fff'
}
}, '12-04', '12-05', '12-05', '12-06', '12-07', '12-08', '12-09', '12-10', '12-11', '12-12', '12-13'],
// 坐标轴轴线相关设置
axisLine: {
// X轴或者Y轴的轴线是否在另一个轴的 0 刻度上,只有在另一个轴为数值轴且包含 0 刻度时有效
onZero: true,
// 坐标轴轴线样式设置
lineStyle: {
color: '#2b7ed1',
width: 1,
// 轴线类型,可选'solid' | 'dashed' | 'dotted'
type: 'solid'
}
},
// 坐标轴刻度相关设置
axisTick: {
// 是否显示刻度
show: true,
// 坐标轴刻度的显示间隔,在类目轴中有效
interval: 0,
// 坐标轴刻度是否朝内,默认朝外
inside: false,
// 设置坐标轴刻度样式
lineStyle: {
color: 'red',
width: 2
}
},
// 坐标轴刻度标签的相关设置
axisLabel: {
// 刻度标签旋转的角度,在类目轴的类目标签显示不下的时候可以通过旋转防止标签之间重叠
rotate: 20,
// 坐标轴刻度标签的显示间隔,在类目轴中有效
interval: 1,
// 坐标轴刻度标签与轴线之间的距离
margin: 10,
color: 'green',
align: 'center',
padding: 10,
backgroundColor: 'red',
},
// 坐标轴在 grid区域中的分隔线
splitLine: {
show: true,
// 坐标轴分隔线的显示间隔,在类目轴中有效。默认同 axisLabel.interval 一样
interval: 0,
lineStyle: {
color: '#000',
type: 'dashed',
width: 2
}
},
// 坐标轴在 grid区域中的分隔区域,默认不显示
splitArea: {
show: true,
interval: 0,
//网格区域样式设置
areaStyle: {
color: 'pink',
opacity: 0.5
}
},
// 鼠标移入出现的提示信息
axisPointer: {
show: true,
// 指示器类型
type: 'shadow',
}
}
}
经过对前面的一些整理,突然发现,麻蛋直接看官网的配置项就好啦!为啥要这么浪费时间,看来我还没掌握好正确学习的姿势:joy:
<template>
<div class="tabbar">
<div class="wrapper" ref="wrapper">
<div class="bscroll-container">
<!-- 刷新提示信息 -->
<div class="top-tip">
<span class="refresh-hook">{{ pulldownMsg }}</span>
</div>
<!-- 内容列表 -->
<ul class="content">
<li v-for="(item, i) in data" :key="i">{{ item }}</li>
</ul>
<!-- 底部提示信息 -->
<div class="bottom-tip">
<span class="loading-hook">{{ pullupMsg }}</span>
</div>
</div>
</div>
<!-- alert提示刷新成功 -->
<div class="alert-hook" :style="{ display: alertHook }">刷新成功</div>
</div>
</template>
<script>
import BScroll from "better-scroll";
let count = 1;
export default {
name: "demo",
data() {
return {
data: [0, 1, 2, 3, 4, 5, 6],
pulldownMsg: "下拉刷新",
pullupMsg: "加载更多",
alertHook: "none"
};
},
methods: {
getData() {
return new Promise(resolve => {
//模拟数据请求
setTimeout(() => {
const arr = [];
for (let i = 0; i < 20; i++) {
arr.push(count++);
}
resolve(arr);
}, 1000);
});
},
refreshalert() {
//刷新成功提示
this.alertHook = "block";
setTimeout(() => {
this.alertHook = "none";
}, 1000);
}
},
created() {
const that = this;
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.wrapper, {
//初始化better-scroll
probeType: 1, //1 滚动的时候会派发scroll事件,会截流。2滚动的时候实时派发scroll事件,不会截流。 3除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
click: true //是否派发click事件
});
// 滑动过程中事件
this.scroll.on("scroll", pos => {
if (pos.y > 30) {
this.pulldownMsg = "释放立即刷新";
}
});
//滑动结束松开事件
this.scroll.on("touchEnd", pos => {
//上拉刷新
if (pos.y > 30) {
setTimeout(() => {
that.getData().then(res => {
//刷新数据
that.data = res;
//恢复刷新提示文本值
that.pulldownMsg = "下拉刷新";
//刷新成功后提示
that.refreshalert();
//刷新列表后,重新计算滚动区域高度
that.scroll.refresh();
});
}, 2000);
} else if (pos.y < this.scroll.maxScrollY - 30) {
//下拉加载
this.pullupMsg = "加载中。。。";
setTimeout(() => {
that.getData().then(res => {
//恢复文本值
that.pullupMsg = "加载更多";
that.data = this.data.concat(res);
that.scroll.refresh();
});
}, 2000);
}
});
});
}
};
</script>
<style lang="less" scoped>
//css
.wrapper {
width: 100%;
// height: 300px;
background: #ccc;
overflow: hidden;
position: relative;
}
li {
line-height: 50px;
border-bottom: 1px solid #ccc;
text-align: center;
}
/* 下拉、上拉提示信息 */
.top-tip {
position: absolute;
top: -40px;
left: 0;
z-index: 1;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
color: #555;
}
.bottom-tip {
width: 100%;
height: 35px;
line-height: 35px;
text-align: center;
color: #777;
background: #f2f2f2;
position: absolute;
bottom: -35px;
left: 0;
}
/* 全局提示信息 */
.alert-hook {
display: none;
position: fixed;
top: 62px;
left: 0;
z-index: 2;
width: 100%;
height: 35px;
line-height: 35px;
text-align: center;
color: #fff;
font-size: 12px;
background: rgba(7, 17, 27, 0.5);
}
</style>
这个问题遇到几次了,百思不得其解。场景再现:当我在template引入本地照片时,能正常显示;但是当我在data中尝试将照片地址引入的时候却报错了。真是奇怪,明明地址是完全正确的,后来找到下面一篇博文,才知道了问题所在
Vue如何在data中正常引入图片路径
最后我的解决方式是通过import引入照片地址。
mounted() {
this.$nextTick(() => {
let ele = document.getElementById('xxxid');
ele .onscroll = () => {
//变量scrollTop是滚动条滚动时,距离顶部的距离
let scrollTop = ele .scrollTop;
//变量windowHeight是可视区的高度
let windowHeight = ele .clientHeight;
//变量scrollHeight是滚动条的总高度
let scrollHeight = ele .scrollHeight + this.btnBarHeight;
let scrollWindow = parseInt(scrollTop) + windowHeight + 100;
//滚动条到底部的条件
if (scrollWindow > scrollHeight) {
if (!this.btnBarFixed) return;
this.btnBarFixed = false;
// 滚动到底部后滚动条的总高度会发生变化,因此需要用该变量解决因此而出现的抖动bug
// 该变量的值根据滚动条总高度的变化量而定
this.btnBarHeight = -95;
} else {
if (this.btnBarFixed) return;
this.btnBarFixed = true;
this.btnBarHeight = 0;
}
};
});
},
有一个功能是这样的:前端需要根据后台返回的数据来渲染出一个多级菜单,这个多级菜单无法确认其层级到底为多少,并且需要将该多级菜单封装成一个组件。
一开始的思路可能是使用遍历对数据进行二次处理,不过使用遍历能带来什么呢?貌似在这个场景中一点用也没有:不确定的数据层级;难以监测的数据变化。
既然这个思路不行,那么我们就得换个思路了。首先,找到需求的关键词: 多级菜单、组件、Vue,根据这三个关键词可以知道该需求和vue的组件使用有一定的关系,那么现在我们前往vue官网看看是否能找到一些有用的信息。
打开久违的vue官网,好家伙,居然更新了这么多内容!快速浏览组件目录,寻找需要的信息,很快就发现了目的地——循环引用。花了十来分钟仔细看了几遍,呃.........好像除了明白以下两点,其他的都看的不知所云
没关系,我们先暂时站在巨人的肩膀上拓展自己的技术视野。首先,打开搜索引擎;然后输入关键词 “vue递归组件实现多级菜单” 。
在网上找了一份简单易懂的代码来进行分析,先看一下父组件代码
<template>
<div class="home">
<div class="wrapper">
<ul>
<com-select v-for="(model, index) in list" :model="model" :key="index"></com-select>
</ul>
</div>
</div>
</template>
<script>
import comSelect from "@/components/comSelect.vue";
export default {
name: "Select",
data() {
return {
list: [
{
menuName: "项目管理",
level: 1,
childTree: [
{
menuName: "项目进度",
level: 2,
childTree: [
{
menuName: "项目一",
level: 3,
childTree: [
{ menuName: "项目一详细信息", level: 4, childTree: [] }
]
},
{
menuName: "项目二",
level: 3,
childTree: [
{ menuName: "项目二详细信息", level: 4, childTree: [] }
]
}
]
},
{
menuName: "任务安排",
level: 2,
childTree: []
}
]
},
{
menuName: "数据统计",
level: 1,
childTree: [
{
menuName: "人口统计",
level: 2,
childTree: []
},
{
menuName: "车辆统计",
level: 2,
childTree: []
}
]
},
{
menuName: "人员管理",
level: 1,
childTree: [
{
menuName: "无敌美少女",
level: 2,
childTree: []
},
{
menuName: "威猛帅男子",
level: 2,
childTree: []
}
]
}
]
};
},
components: {
"com-select": comSelect
}
};
</script>
<style scoped>
ul {
list-style: none;
padding: 0;
margin: 0 0 0 10px;
}
li {
cursor: pointer;
padding: 10px;
margin-bottom: 5px;
border: 1px solid #666;
width: 200px;
}
</style>
根据分析或者测试可知,在父组件中,其实是遍历出了list.length个子组件,同时分别将list数组中的顶层数据传递给子组件。
父组件做的事情就这么多,下面我们来看看子组件做的事情
<template>
<li>
<div @click="toggle(model)">{{model.menuName}}</div>
<transition name="slide">
<ul v-show="open">
<com-select v-for="(item,index) in model.childTree" :model="item" :key="index"></com-select>
</ul>
</transition>
</li>
</template>
<script>
export default {
name: "comSelect",
props: {
model: {
type: Object,
required: false
}
},
data() {
return {
open: false
};
},
methods: {
toggle(m) {
console.log(m);
this.open = !this.open;
}
}
};
</script>
<style scoped>
ul {
list-style: none;
text-align: left;
padding: 0;
margin: 0 0 0 10px;
}
.slide-enter-active,
.slide-leave-active {
transition: all 0.2s;
}
.slide-enter,
.slide-leave-to {
transform: translateY(-20px);
}
.slide-enter-to,
.slide-leave {
transform: translateY(0px);
}
</style>
看到子组件的代码,我们先思考在官网看懂的两个问题:1、是否有name值?2、是否含条件性?很明显,子组件存在name值;当然也存在条件性(当model.childTree的length等于0的时候)。那么下面我们分析下子组件中到底发生了什么事
由于上面的例子在父组件中做了子组件该做的事情,所以这是不太合理的,因此我们改写一下代码
<template>
<div id="select">内容
<div class="wrapper">
<ul>
<com-select :model="list"></com-select>
</ul>
</div>
</div>
</template>
子组件
<template>
<div>
<li v-for="(items,index) in model" :key="index">
<div @click="toggle(index)">{{items.menuName}}</div>
<transition name="slide">
<ul v-if="openIndex===index">
<com-select :model="items.childTree" :key="index"></com-select>
</ul>
</transition>
</li>
</div>
</template>
<script>
export default {
name: "comSelect",
props: {
model: {
type: Array | Object,
required: false
}
},
data() {
return {
openIndex: ""
};
},
methods: {
toggle(m) {
this.openIndex = m;
}
}
};
</script>
<style scoped>
ul {
list-style: none;
text-align: left;
padding: 0;
margin: 0 0 0 10px;
}
.slide-enter-active,
.slide-leave-active {
transition: all 0.2s;
}
.slide-enter,
.slide-leave-to {
transform: translateY(-20px);
}
.slide-enter-to,
.slide-leave {
transform: translateY(0px);
}
</style>
到这就完美了吗?其实并没有,以上代码还是存在着问题:切换状态存储单一。这是与需求不符合的,我们再来修改下子组件的代码
<template>
<div>
<li v-for="(items,index) in model" :key="index">
<div @click="toggle(items.childTree,index)">{{items.menuName}}</div>
<transition name="slide">
<ul>
<com-select v-if="statusObj[index]" :model="items.childTree" :key="index"></com-select>
</ul>
</transition>
</li>
</div>
</template>
<script>
export default {
name: "comSelect",
props: {
model: {
type: Array,
required: false
}
},
data() {
return {
statusObj: {}
};
},
methods: {
toggle(isChil, index) {
if (isChil.length > 0) {
this.$set(this.statusObj, index, !this.statusObj[index]);
}
}
}
};
</script>
<style scoped>
ul {
list-style: none;
text-align: left;
padding: 0;
margin: 0 0 0 10px;
}
.slide-enter-active,
.slide-leave-active {
transition: all 0.2s;
}
.slide-enter,
.slide-leave-to {
transform: translateY(-20px);
}
.slide-enter-to,
.slide-leave {
transform: translateY(0px);
}
</style>
为了加深印象,我们用JavaScript写一个类似的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
let tree = [{
id: 1,
label: "1级目录1",
children: [{
id: "1-1",
label: "1.1目录"
},
{
id: "1-2",
label: "1.2目录"
},
{
id: "1-3",
label: "1.3目录"
},
]
},
{
id: 2,
label: "1级目录2",
},
{
id: 3,
label: "1级目录3",
children: [{
id: "3-1",
label: "3.1目录"
},
{
id: "3-2",
label: "3.2目录",
children: [{
id: "3-2-1",
label: "3.2.1目录"
},
{
id: "3-2-2",
label: "3.2.2目录"
},
{
id: "3-2-3",
label: "3.2.3目录"
}
]
}
]
},
{
id: 4,
label: "1级目录4",
children: [{
id: "4-1",
label: "4.1目录"
},
{
id: "4-2",
label: "4.2目录",
children: [{
id: "4-2-1",
label: "4.2.1目录"
}]
}
]
},
{
id: 5,
label: "1级目录5",
children: [{
id: "5-1",
label: "5.1目录",
children: [{
id: "5-1-1",
label: "5.1.1目录"
},
{
id: "5-1-2",
label: "5.1.2目录",
children: [{
id: "5-1-2-1",
label: "5.1.2.1目录"
}, ]
}
]
},
{
id: "5-2",
label: "5.2目录"
}
]
},
];
let render = function (tree) {
if (!tree) return null
let ul = document.createElement('ul');
for (let i = 0; i < tree.length; i++) {
let li = document.createElement('li')
// 创建span标签
let span = document.createElement('span');
span.innerText = tree[i].label;
li.appendChild(span);
li.onclick = function () {
let ulNode = this.getElementsByTagName('ul')[0]
if (!ulNode) return null
let status = window.getComputedStyle(ulNode).display
event.stopPropagation();
if (status == 'block') {
ulNode.style.display = 'none'
} else {
ulNode.style.display = 'block'
}
}
if (tree[i].children) {
let sub = render(tree[i].children);
li.appendChild(sub);
}
ul.appendChild(li);
}
return ul
};
document.body.innerHTML = '';
document.body.appendChild(render(tree));
</script>
</body>
</html>
在最近一个项目中,有一个问题百思不得其解,就是由最新的webpack4.0+vue-cli3.0搭建起来的项目中缺少static文件夹,导致了我一直无法将本地mock的json数据引入到项目中。经过一段时间的琢磨,终于有了一个方法。
使用import把json文件中的数据导入进组件中,下面是项目结构图
test.json文件的目录内容如下
{
"goods": [
{
"id":"21474836481",
"name":"电商平台",
"gid":"0",
"desc":"包括综合电商、返利平台、电商导购,不包括垂直类电商"
},
{
"id":"21474836581",
"name":"综合电商",
"gid":"21474836481",
"desc":"通过网络提供3种及以上不同行业/品类商品,例:京东、有赞等"
},
{
"id":"21474836585",
"name":"返利平台",
"gid":"21474836481",
"desc":"购物后提供返利/返现的电商类平台,例:返利网等"
},
{
"id":"21474836586",
"name":"电商导购",
"gid":"21474836481",
"desc":"导购平台,不产生在线交易的资讯平台,例:什么值得买等"
}
]
}
在组件中进行引入,此时就将本地json中的数据全部引入进来了。
<script>
import {goods} from "@/store/test.json";
export default {
name: "Acenter",
data() {
return {}
},
created() {
this.$nextTick(() => {
this.getGoods();
});
},
methods: {
getGoods() {
console.log(goods)
},
}
}
</script>
把以下代码复制到.postcssrc.js文件中
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
"postcss-aspect-ratio-mini": {},
"postcss-write-svg": {
utf8: false
},
"postcss-cssnext": {},
"postcss-px-to-viewport": {
viewportWidth: 750,
unitPrecision: 3,
viewportUnit: 'vw',
selectorBlackList: ['.ignore', '.hairlines'],
minPixelValue: 1,
mediaQuery: false
},
"postcss-viewport-units": {},
}
}
然后分别安装以下模块
最后可以写些css代码,然后看看源码是否被转义成vw单位
.demo {
height: 15px;
width: 750px;
}
更多适配资料可以点击这里
import moment from "moment";
const filters = {
// 时间过滤器
formatDate(toFormatDate, fmt = "YYYY-MM-DD HH:mm:ss") {
if (toFormatDate) {
return moment(toFormatDate).format(fmt);
}
},
// 自定义时间过滤器
formaDateCustom(value, fmt) {
var date = new Date(value);
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"w+": date.getDay(), //星期
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
S: date.getMilliseconds(), //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (var k in o) {
if (k === "w+") {
if (o[k] === 0) {
fmt = fmt.replace("w", "周日");
} else if (o[k] === 1) {
fmt = fmt.replace("w", "周一");
} else if (o[k] === 2) {
fmt = fmt.replace("w", "周二");
} else if (o[k] === 3) {
fmt = fmt.replace("w", "周三");
} else if (o[k] === 4) {
fmt = fmt.replace("w", "周四");
} else if (o[k] === 5) {
fmt = fmt.replace("w", "周五");
} else if (o[k] === 6) {
fmt = fmt.replace("w", "周六");
}
} else if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1
? o[k]
: ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;
},
// 空格过滤器
trim(value, type) {
//去除空格 type 1-所有空格 2-前后空格 3-前空格 4-后空格
switch (type) {
case 1:
return value.replace(/\s+/g, "");
case 2:
return value.replace(/(^\s*)|(\s*$)/g, "");
case 3:
return value.replace(/(^\s*)/g, "");
case 4:
return value.replace(/(\s*$)/g, "");
default:
return value;
}
},
// 字母大小写切换
changeStrCase(str, type) {
// type 1:首字母大写 2:首页母小写 3:大小写转换 4:全部大写 5:全部小写
function ToggleCase(strItem) {
var itemText = "";
strItem.split("").forEach(function (item) {
if (/^([a-z]+)/.test(item)) {
itemText += item.toUpperCase();
} else if (/^([A-Z]+)/.test(item)) {
itemText += item.toLowerCase();
} else {
itemText += item;
}
});
return itemText;
}
switch (type) {
case 1:
return str.replace(/\b\w+\b/g, function (word) {
return (
word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase()
);
});
case 2:
return str.replace(/\b\w+\b/g, function (word) {
return (
word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase()
);
});
case 3:
return ToggleCase(str);
case 4:
return str.toUpperCase();
case 5:
return str.toLowerCase();
default:
return str;
}
},
// 字符串循环复制,count->次数
repeatStr(str, count) {
var text = "";
for (var i = 0; i < count; i++) {
text += str;
}
return text;
},
// 字符串替换
replaceStrAll(str, AFindText, ARepText) {
var raRegExp = new RegExp(AFindText, "g");
return str.replace(raRegExp, ARepText);
},
// 字符替换*,隐藏手机号或者身份证号等
// replaceStrIcard(字符串,字符格式,替换方式,替换的字符(默认*))
replaceStrIcard(str, regArr, type, ARepText) {
var regtext = "";
var Reg = null;
var replaceText = ARepText || "*";
function repeatRepStr(repstr, count) {
var text = "";
for (var i = 0; i < count; i++) {
text += repstr;
}
return text;
}
if (regArr.length === 3 && type === 0) {
regtext =
"(\\w{" +
regArr[0] +
"})\\w{" +
regArr[1] +
"}(\\w{" +
regArr[2] +
"})";
Reg = new RegExp(regtext);
var replaceCount = repeatRepStr(replaceText, regArr[1]);
return str.replace(Reg, "$1" + replaceCount + "$2");
} else if (regArr.length === 3 && type === 1) {
regtext =
"\\w{" + regArr[0] + "}(\\w{" + regArr[1] + "})\\w{" + regArr[2] + "}";
Reg = new RegExp(regtext);
var replaceCount1 = repeatRepStr(replaceText, regArr[0]);
var replaceCount2 = repeatRepStr(replaceText, regArr[2]);
return str.replace(Reg, replaceCount1 + "$1" + replaceCount2);
} else if (regArr.length === 1 && type === 0) {
regtext = "(^\\w{" + regArr[0] + "})";
Reg = new RegExp(regtext);
var replaceCountItem = repeatRepStr(replaceText, regArr[0]);
return str.replace(Reg, replaceCountItem);
} else if (regArr.length === 1 && type === 1) {
regtext = "(\\w{" + regArr[0] + "}$)";
Reg = new RegExp(regtext);
var replaceCountChild = repeatRepStr(replaceText, regArr[0]);
return str.replace(Reg, replaceCountChild);
}
},
// 格式化处理字符串
formatText(str, size, delimiter) {
var _size = size || 3;
var _delimiter = delimiter || ",";
var regText = "\\B(?=(\\w{" + _size + "})+(?!\\w))";
var reg = new RegExp(regText, "g");
return str.replace(reg, _delimiter);
},
// 现金额大写转换函数
upDigit(n) {
var fraction = ["角", "分", "厘"];
var digit = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
var unit = [
["元", "万", "亿"],
["", "拾", "佰", "仟"],
];
var head = n < 0 ? "欠人民币" : "人民币";
n = Math.abs(n);
var s = "";
for (var i = 0; i < fraction.length; i++) {
s += (
digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]
).replace(/零./, "");
}
s = s || "整";
n = Math.floor(n);
for (var i = 0; i < unit[0].length && n > 0; i++) {
var p = "";
for (var j = 0; j < unit[1].length && n > 0; j++) {
p = digit[n % 10] + unit[1][j] + p;
n = Math.floor(n / 10);
}
s = p.replace(/(零.)*零$/, "").replace(/^$/, "零") + unit[0][i] + s;
}
return (
head +
s
.replace(/(零.)*零元/, "元")
.replace(/(零.)+/g, "零")
.replace(/^整$/, "零元整")
);
},
// 保留2位小数
toDecimal2(x) {
var f = parseFloat(x);
if (isNaN(f)) {
return false;
}
f = Math.round(x * 100) / 100;
var s = f.toString();
var rs = s.indexOf(".");
if (rs < 0) {
rs = s.length;
s += ".";
}
while (s.length <= rs + 2) {
s += "0";
}
return s;
},
};
export default filters;
import Vue from 'vue'
import App from './App'
import router from './router/index'
// 全局过滤器
import filters from './utils/filters'
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key]);
})
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
<template>
<div>
<h3>1.时间过滤器-moment</h3>
<h5>{{time | formatDate}}</h5>
<h5>{{time | formatDate('YYYY-MM-DD')}}</h5>
<hr>
<h3>2.时间过滤器-自定义</h3>
<h5>{{time | formaDateCustom('YYYY/MM')}}</h5>
<h5>{{time | formaDateCustom('YYYY-MM-DD')}}</h5>
<h5>{{time | formaDateCustom('YYYY-MM-DD w')}}</h5>
<hr>
<h3>3.空格过滤器</h3>
<h5>英勇{{str | trim(1)}}</h5>
<h5>英勇{{str | trim(2)}}</h5>
<h5>英勇{{str | trim(3)}}</h5>
<h5>英勇{{str | trim(4)}}</h5>
<hr>
<h3>4.大小写转换过滤器</h3>
<h5>{{str | changeStrCase(1)}}</h5>
<h5>{{str | changeStrCase(2)}}</h5>
<h5>{{str | changeStrCase(3)}}</h5>
<h5>{{str | changeStrCase(4)}}</h5>
<h5>{{str | changeStrCase(5)}}</h5>
<hr>
<h3>5.字符串循环复制</h3>
<h5>{{strItem | repeatStr(1)}}</h5>
<h5>{{strItem | repeatStr(2)}}</h5>
<h5>{{strItem | repeatStr(3)}}</h5>
<hr>
<h3>6.字符串替换</h3>
<h5>{{strItem | replaceStrAll('A','K')}}</h5>
<h5>{{strItem | replaceStrAll('B','K')}}</h5>
<hr>
<h3>7.字符替换*,隐藏手机号或者身份证号等</h3>
<h5>{{'15920902366' | replaceStrIcard([5],0)}}</h5>
<h5>{{'15920902366' | replaceStrIcard([5],1)}}</h5>
<h5>{{'15920902366' | replaceStrIcard([3,5,3],0)}}</h5>
<h5>{{'15920902366' | replaceStrIcard([3,5,3],1)}}</h5>
<h5>{{'15920902366' | replaceStrIcard([3,5,3],1,'+')}}</h5>
<hr>
<h3>8.格式化处理字符串</h3>
<h5>{{'15920902366' | formatText}}</h5>
<h5>{{'15920902366' | formatText(4,' ')}}</h5>
<h5>{{'15920902366' | formatText(4,',')}}</h5>
<h5>{{'15920902366' | formatText(4,'- -')}}</h5>
<hr>
<h3>9.现金额大写转换函数</h3>
<h5>{{'15920902366' | upDigit}}</h5>
<h5>{{'902366' | upDigit}}</h5>
<h5>{{'-902366' | upDigit}}</h5>
<hr>
<h3>10.保留2位小数</h3>
<h5>{{'15920902366' | toDecimal2}}</h5>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
time: "2021-01-31T15:59:59.000+00:00",
str: " Afdklsa FjdLsa teruot 453",
strItem: "AB"
}
},
methods: {}
}
</script>
import Vue from 'vue'
import App from './App'
import router from './router'
import 'mint-ui/lib/style.css'
import { Loadmore } from 'mint-ui';
Vue.component(Loadmore.name, Loadmore);
import { Spinner } from 'mint-ui';
Vue.component(Spinner.name, Spinner);
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<title>Demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but s3_recharge doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
<script>
//屏幕适配
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var html = document.documentElement;
var k = 740;
html.style.fontSize = html.clientWidth / k * 100 + "px";
}
setFont();
setTimeout(function () {
setFont();
}, 300);
doc.addEventListener('DOMContentLoaded', setFont, false);
win.addEventListener('resize', setFont, false);
win.addEventListener('load', setFont, false);
document.documentElement.addEventListener('touchstart', function (event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, false);
var lastTouchEnd = 0;
document.documentElement.addEventListener('touchend', function (event) {
var now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 解决ios safari无法禁止双指缩放问题
document.addEventListener('gesturestart', function (event) {
event.preventDefault();
});
})(window, document);
</script>
</html>
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family: 'Microsoft YaHei UI';
box-sizing: border-box;
position: relative;
z-index: -1;
}
h1,
h2,
h3,
h4,
h5,
p,
ul,
li,
button,
a {
padding: 0;
margin: 0;
font-weight: normal;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
button {
border: none;
outline: none
}
/* 金黄色 */
.golden {
color: #ec9c00;
}
/* 灰色背景色 */
.graybgc {
background-color: #eee;
}
/* 深灰色字体色 */
.graytext {
color: #717171;
}
span {
display: inline-block;
vertical-align: middle;
}
编写结构代码
<template>
<div>
<div
class="page-loadmore-wrapper"
:style="{ height: wrapperHeight + 'px' }"
>
<mt-spinner
v-show="list < 1 && InitialLoading"
color="#26a2ff"
class="center"
></mt-spinner>
<mt-loadmore
:top-method="loadTop"
@top-status-change="handleTopChange"
:bottom-method="loadBottom"
@bottom-status-change="handleBottomChange"
:bottom-all-loaded="allLoaded"
:auto-fill="false"
ref="loadmore"
>
<!-- :auto-fill="true" 时页面加载完毕时 默认执行loadBottom 值为false时 自己写一个加载 -->
<div class="hot-list">
<div
class="hot-one hot-item"
v-for="(item, index) in list"
:key="index"
>
<a href="javascript:;" class="show clearfix">
<div class="img-box">
<img
src="http://qn2.wkmblog.com/FrBnGzapQmFwvb-PhspYaxkXE7_T?imageView2/1/w/160/h/90"
class="fl"
/>
</div>
<h5 class="white-space">别让错误的认知毁掉我们的人生</h5>
<p>
女孩天生就不擅长数学、女孩学不好数学是正常的。因为<span
class="color_e85647"
>...详情</span
>
</p>
<p class="read">
<span class="fa fa-eye"></span> 391
<span class="fa fa-pencil-square-o"></span> 10
</p>
</a>
</div>
</div>
<div slot="top" class="mint-loadmore-top" style="text-align:center">
<span
v-show="topStatus !== 'loading'"
:class="{ 'is-rotate': topStatus === 'drop' }"
>↓</span
>
<mt-spinner
v-show="topStatus == 'loading'"
color="#26a2ff"
></mt-spinner>
<span class="mint-loadmore-text">{{ topText }}</span>
</div>
<div slot="bottom" class="mint-loadmore-bottom">
<span
v-show="bottomStatus !== 'loading'"
:class="{ 'is-rotate': bottomStatus === 'drop' }"
>↑</span
>
<mt-spinner
v-show="bottomStatus == 'loading'"
color="#26a2ff"
></mt-spinner>
<span class="mint-loadmore-text">{{ bottomText }}</span>
</div>
<div v-if="allLoaded" style="text-align:center;" class="data-none">
没有更多数据了~
</div>
</mt-loadmore>
</div>
</div>
</template>
编写样式代码
<style scoped lang="less">
.page-loadmore-wrapper {
overflow: scroll;
z-index: 100;
}
.hot-list {
padding: 0 0.4rem;
}
.hot-item {
padding: 0.3rem 0;
}
.hot-one {
overflow: hidden;
border-bottom: 0.02rem dashed #ccc;
}
.hot-one a img {
padding-right: 0.1rem;
}
.hot-item a img {
height: 0.9rem;
width: 1.35rem;
}
.fl {
float: left;
}
.hot-one a h5 {
margin-top: 2px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin-bottom: 6px;
font-size: 0.24rem;
color: #000;
text-align: left;
}
.hot-one a p {
font-size: 0.18rem;
color: #828282;
margin: 0 0 3px;
text-align: left;
}
.hot-one a .read {
text-align: left;
}
.color_e85647 {
color: #e85647;
}
div.hot-list > div:first-child .img-box {
overflow: hidden;
}
div.hot-list > div:first-child img {
width: 100%;
height: auto;
padding-right: 0;
}
.mint-loadmore-top {
margin-top: -1rem;
font-size: 0.3rem;
}
.mint-loadmore-bottom {
font-size: 0.3rem;
}
.data-none {
font-size: 0.3rem;
}
</style>
编写js代码
<script>
export default {
name: "Loadmore",
data() {
return {
topText: "",
topPullText: "下拉刷新",
topDropText: "释放更新",
topLoadingText: "加载中...",
bottomText: "",
bottomPullText: "上拉刷新",
bottomDropText: "释放更新",
bottomLoadingText: "加载中...",
examplename: "Loadmore",
pageNum: 1, //页码
InitialLoading: true, //初始加载
list: [], //数据
allLoaded: false, //数据是否加载完毕
bottomStatus: "", //底部上拉加载状态
wrapperHeight: 0, //容器高度
topStatus: "" //顶部下拉加载状态
};
},
watch: {
topStatus(val) {
switch (val) {
case "pull":
this.topText = this.topPullText;
break;
case "drop":
this.topText = this.topDropText;
break;
case "loading":
this.topText = this.topLoadingText;
break;
}
},
bottomStatus(val) {
switch (val) {
case "pull":
this.bottomText = this.bottomPullText;
break;
case "drop":
this.bottomText = this.bottomDropText;
break;
case "loading":
this.bottomText = this.bottomLoadingText;
break;
}
}
},
mounted() {
let windowWidth = document.documentElement.clientWidth; //获取屏幕宽度
if (windowWidth >= 768) {
//这里根据自己的实际情况设置容器的高度
this.wrapperHeight = document.documentElement.clientHeight - 105;
} else {
this.wrapperHeight = document.documentElement.clientHeight - 80;
}
setTimeout(() => {
//页面挂载完毕 模拟数据请求 这里为了方便使用一次性定时器
this.getOrderList();
}, 1500);
},
methods: {
// 获取订单列表数据
getOrderList(status) {
let data = [1, 2, 3]; //模拟接口返回的数据
if (data && data.length > 0) {
if (status == "up") {
this.list = this.list.concat(data);
this.handleBottomChange("uploadingEnd"); //数据加载完毕 修改状态码
this.$refs.loadmore.onBottomLoaded();
} else if (status == "down") {
this.list = data;
this.handleTopChange("downloadingEnd"); //数据加载完毕 修改状态码
this.$refs.loadmore.onTopLoaded();
} else {
this.list = data;
}
} else {
//没有数据且上拉
if (status == "up") {
this.allLoaded = true; //模拟数据加载完毕 禁用上拉加载
return;
}
}
},
handleBottomChange(status) {
this.bottomStatus = status;
},
loadBottom() {
this.handleBottomChange("uploading"); //上拉时 改变状态码
this.pageNum += 1;
setTimeout(() => {
this.getOrderList("up");
}, 1500);
},
handleTopChange(status) {
this.topStatus = status;
},
loadTop() {
//下拉刷新 模拟数据请求这里为了方便使用一次性定时器
this.handleTopChange("downloading"); //下拉时 改变状态码
this.pageNum = 1;
this.allLoaded = false; //下拉刷新时解除上拉加载的禁用
setTimeout(() => {
this.getOrderList("down");
}, 1500);
}
}
};
</script>
本篇blog基本上完全借鉴此处,推荐结合进行食用。
之前只是一个简单的小demo,下面简单谈谈在实际项目中是如何运行的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<title>Demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but s3_recharge doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
<script>
//屏幕适配
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var html = document.documentElement;
var k = 740;
html.style.fontSize = html.clientWidth / k * 100 + "px";
}
setFont();
setTimeout(function () {
setFont();
}, 300);
doc.addEventListener('DOMContentLoaded', setFont, false);
win.addEventListener('resize', setFont, false);
win.addEventListener('load', setFont, false);
document.documentElement.addEventListener('touchstart', function (event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, false);
var lastTouchEnd = 0;
document.documentElement.addEventListener('touchend', function (event) {
var now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 解决ios safari无法禁止双指缩放问题
document.addEventListener('gesturestart', function (event) {
event.preventDefault();
});
})(window, document);
</script>
</html>
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family: 'Microsoft YaHei UI';
box-sizing: border-box;
position: relative;
z-index: -1;
}
h1,
h2,
h3,
h4,
h5,
p,
ul,
li,
button,
a {
padding: 0;
margin: 0;
font-weight: normal;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
button {
border: none;
outline: none
}
/* 金黄色 */
.golden {
color: #ec9c00;
}
/* 灰色背景色 */
.graybgc {
background-color: #eee;
}
/* 深灰色字体色 */
.graytext {
color: #717171;
}
span {
display: inline-block;
vertical-align: middle;
}
编写结构代码
<template>
<div class="order-list graybgc">
<div class="order-wrapper" ref="wrapper">
<div
class="order-info"
ref="itemwrapper"
v-show="dealList && dealList.length > 0"
>
<div>
<!-- 刷新提示信息 -->
<div class="top-tip" v-if="isPulldown">
<span>重新加载~</span>
</div>
<div class="order-box" v-for="(item, index) in dealList" :key="index">
<div class="header">
<div class="user-info">
<span class="game-name">王者荣耀</span>
<span class="game-serve">李小萌-微信17区</span>
</div>
<div class="order-status">
<span class="no-play-btn">去支付</span>
</div>
</div>
<div class="info">
<div class="info-img">
<img
src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583233053177&di=350747e7d50ea15f56c08a0d1441fb01&imgtype=0&src=http%3A%2F%2Fphotocdn.sohu.com%2F20110814%2FImg316287483.jpg"
alt=""
/>
</div>
<div class="info-number">
<div class="title">
每日登录礼包
</div>
<div class="money graytext">
单价:¥20 / 件
</div>
<div class="number graytext">数量:10 件</div>
</div>
</div>
<div class="footer">
<div class="time">
<span class="date">02-10 22:00</span>
</div>
<div class="total-money">
<span class="text">合计:</span>
<div class="money">
<span class="min">¥</span>
<span>200</span>
</div>
</div>
</div>
</div>
<div class="null-box"></div>
<div class="bottom-tip" v-if="isPullup">
{{ canPullup ? "松手加载更多~" : "没有更多数据了~" }}
</div>
<div class="null-box"></div>
</div>
</div>
<div class="order-no" v-if="!dealList || dealList.length == 0">
<div class="img"></div>
<div class="desc">暂时还没您的购买记录哦</div>
</div>
</div>
<div class="order-footer">
当前页面仅提供14天记录查询,如有疑问,请
<a href="#">联系客服</a>
</div>
</div>
</template>
编写样式代码
<style scoped lang="less">
.order-list {
position: relative;
width: 100%;
min-height: 100%;
padding: 0.25rem 0.4rem 0;
box-sizing: border-box;
.order-wrapper {
height: 90vh;
.order-info {
width: 100%;
height: 91.1vh;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
.top-tip {
margin-top: .2rem;
width: 100%;
height: 0.5rem;
line-height: 0.1rem;
text-align: center;
span {
font-size: 0.2rem;
}
}
.order-box {
background-color: #fff;
padding: 0.15rem 0.3rem;
box-sizing: border-box;
border-radius: 0.05rem;
font-size: 0.2rem;
margin-bottom: 0.3rem;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.user-info {
color: #000;
text-align: left;
.game-name {
margin-right: 0.1rem;
}
}
.order-status {
.no-play-btn {
padding: 0.01rem 0.2rem;
background-color: #ec6f00;
color: #fff;
border-radius: 0.2rem;
margin-left: 0.1rem;
}
}
}
.info {
display: flex;
width: 100%;
padding: 0.1rem 0 0.15rem;
box-sizing: border-box;
border-bottom: 0.02rem solid #d5d5d5;
.info-img {
display: inline-block;
vertical-align: top;
width: 1.1rem;
height: 1.1rem;
background-color: #ec6f00;
text-align: center;
line-height: 1.1rem;
margin-right: 0.2rem;
position: relative;
img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-block;
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
}
}
.info-number {
display: inline-block;
vertical-align: top;
height: 1.1rem;
text-align: left;
padding: 0;
margin: 0;
.title {
font-size: 0.28rem;
color: #000;
}
}
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 0.15rem;
.money {
display: inline-block;
vertical-align: middle;
font-size: 0.28rem;
color: #000;
span {
vertical-align: baseline;
}
.min {
font-size: 0.22rem;
}
}
}
}
.null-box {
width: 100%;
height: 1px;
}
.bottom-tip {
text-align: center;
font-size: 0.2rem;
color: #999;
margin-top: -0.2rem;
margin-bottom: 0.3rem;
}
}
.order-no {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) !important;
width: 4rem;
height: 5rem;
text-align: center;
.img {
width: 1.38rem;
height: 1.8rem;
background-color: #999;
margin: 0 auto;
}
.desc {
margin-top: 0.4rem;
font-size: 0.3rem;
}
}
}
.order-footer {
width: 100%;
height: 0.6rem;
line-height: 0.6rem;
position: fixed;
bottom: 0;
left: 0;
font-size: 0.24rem;
background-color: inherit;
z-index: 1;
text-align: center;
a {
text-decoration: underline;
color: #000;
}
}
}
</style>
编写js代码
import BScroll from "better-scroll";
export default {
name: "OrderList",
data() {
return {
isPulldown: false,
isPullup: false,
pageIndex: 1,
dealList: [], //订单记录列表
canPullup: true,
lock: false //避免同一时间多次请求数据
};
},
created() {
this.getOrderList();
this.$nextTick(() => {
this.scorllEvent();
});
},
methods: {
// 下拉刷新上拉加载
scorllEvent() {
this.scroll = new BScroll(this.$refs.wrapper, {
probeType: 1,
click: true
});
this.childScroll = new BScroll(this.$refs.itemwrapper, { // 解决在真机中无法滚动问题
mouseWheel: true,
scrollY: true,
click: true
});
// 滑动过程中事件
this.scroll.on("scroll", pos => {
if (this.lock) return;
if (pos.y > 40) {
this.isPulldown = true;
} else if (pos.y < -45) {
this.isPullup = true;
}
});
//滑动结束松开事件
this.scroll.on("touchEnd", pos => {
if (this.lock) return;
if (pos.y > 40 && this.isPulldown) {
//下拉刷新
this.pageIndex = 1;
this.getOrderList("down");
} else if (pos.y < this.scroll.maxScrollY - 45 && this.isPullup) {
//上拉加载
if (this.canPullup) {
this.pageIndex++;
this.getOrderList("up");
}
}
});
},
// 获取订单列表数据
getOrderList(status) {
let data = [1, 2, 3]; //模拟接口返回的数据
if (data && data.length > 0) {
if (status == "up") {
this.dealList = this.dealList.concat(data);
} else {
this.dealList = data;
}
} else { //没有数据且上拉
if (status == "up") {
this.canPullup = false;
return;
}
}
if (status) {
this.lock = true;
this.scroll.refresh(); // 刷新列表后,重新计算滚动区域高度
this.dataTransition(status);
}
},
// 数据过渡效果
dataTransition(status) {
setTimeout(() => {
this.isPullup = false;
this.isPulldown = false;
this.refreshalert(status);
}, 1000);
},
// 数据刷新/加载成功
refreshalert(status) {
setTimeout(() => {
alert(`${status == "down" ? "刷新成功" : "加载成功"}`);
this.lock = false;
}, 1200);
}
}
};
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.