Vue 所有代码资料仓库
# Vue2.0 的使用
# src - 分析脚手架
# 脚手架文件结构
├── node_modules: node 模块 | |
├── public: 编译后的文件 | |
│ ├── favicon.ico: 页签图标 | |
│ └── index.html: 主页面 | |
├── src: 源文件 | |
│ ├── assets: 存放静态资源 | |
│ │ └── logo.png: logo | |
│ │── component: 存放组件 | |
│ │ └── HelloWorld.vue: vue 组件 | |
│ │── router: 路由配置 | |
│ │ └── index.js: 路由配置文件 | |
│ │── App.vue: 汇总所有组件 | |
│ │── main.js: 入口文件 | |
├── .gitignore: git 版本管制忽略的配置 | |
├── babel.config.js: babel 的配置文件 | |
├── package.json: 应用包配置文件 | |
├── README.md: 应用描述文件 | |
├── package-lock.json: 包版本控制文件 |
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
/** | |
* 关于不同版本的 Vue: | |
1.vue.html 与 vue.runtime.xxx.html 的区别: | |
(1).vue.html 是完整版的 Vue,包含:核心功能 + 模板解析器 | |
(2).vue.runtime.xxx.html 是运行版的 Vue,只包含:核心功能;没有模板解析器。 | |
2. 因为 vue.runtime.xxx.html 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容。 | |
*/ | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<img src="./assets/logo.png"> | |
<School></School> | |
<Student></Student> | |
</div> | |
</template> | |
<script> | |
// 引入组件 | |
import School from "./components/School"; | |
import Student from "./components/Student"; | |
//export default: 暴露组件可以让别人导入此文件 | |
export default { | |
name: "App", | |
components: { | |
School, | |
Student, | |
}, | |
};</script> | |
<style> | |
</style> |
<template> | |
<div class="demo"> | |
<h2>学校名称:{ { sName } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
<button @click="show">点我提示信息</button> | |
</div> | |
</template> | |
<script> | |
// 组件交互相关的代码(数据、方法等等) | |
export default { | |
name: "School", | |
data() { | |
return { | |
sName: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
methods: { | |
show() { | |
alert(this.sName); | |
}, | |
}, | |
};</script> | |
<style> | |
/* 组件的样式 */ | |
.demo { | |
background-color: aqua; | |
} | |
</style> |
<template> | |
<div> | |
<h2>学生姓名:{ { stName } }</h2> | |
<h2>学生年龄:{ { age } }</h2> | |
</div> | |
</template> | |
<script> | |
// 组件交互相关的代码(数据、方法等等) | |
export default { | |
data() { | |
return { | |
stName: "小明", | |
age: 18, | |
}; | |
}, | |
};</script> |
# src-ref 属性
ref 属性介绍:
- 被用来给元素或子组件注册引用信息(id 的替代者)。
- 应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)。
- 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1>
<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<h1 v-text="msg" ref="title"></h1> | |
<button ref="btn" @click="showDemo">点我获取上方Demo</button> | |
<School ref="sch"></School> | |
</div> | |
</template> | |
<script> | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
School, | |
}, | |
data() { | |
return { | |
msg: "欢迎学习Vue", | |
}; | |
}, | |
methods: { | |
showDemo() { | |
console.log(this.$refs.title);// 真实 Demo 元素 | |
console.log(this.$refs.btn);// 真实 Demo 元素 | |
console.log(this.$refs.sch);//School 组件实例对象 (Vc) | |
}, | |
}, | |
};</script> | |
<style> | |
</style> |
<template> | |
<div class="school"> | |
<h2>学校名称:{ { name } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "School", | |
data() { | |
return { | |
name:'尚硅谷', | |
} | |
}, | |
};</script> | |
<style> | |
.school{ | |
background-color: deeppink; | |
} | |
</style> |
# src-props 配置
props 配置项介绍:
- 功能:让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/>
- 接收数据:
第一种方式(只接收):
props:['name']
第二种方式(限制类型):
props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, // 类型
required:true, // 必要性
default:'老王' // 默认值
}
}
备注:props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据。
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<Student name="李四" sex="女" :age="20"></Student> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
export default { | |
name: "App", | |
components: { | |
Student, | |
}, | |
};</script> | |
<style> | |
</style> |
<template> | |
<div class="school"> | |
<h1>{ { msg } }</h1> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
<h2>学生年龄:{ { myAge + 1 } }</h2> | |
<button @click="updateAge">点我更新年龄</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Student", | |
data() { | |
return { | |
msg: "我是尚硅谷的学生", | |
myAge: this.age, | |
}; | |
}, | |
methods:{ | |
updateAge(){ | |
this.myAge++; | |
} | |
}, | |
//props:['name','sex','age'], // 方式一 props 配置接收 App.vue 传来的外部参数 | |
// 方式二 接收的同时对数据:进行类型限制 | |
/* props:{ | |
name:String, | |
sex:String, | |
age:Number, | |
} */ | |
// 方式三 接收的同时对数据:进行类型限制 + 默认值的指定 + 必要性的限制 | |
props: { | |
name: { | |
type: String, //name 的类型 | |
required: true, //name 是必须的 | |
}, | |
age: { | |
type: Number, | |
default: 18, // 不传参数默认值 18 | |
}, | |
sex: { | |
type: String, | |
require: true, | |
}, | |
}, | |
};</script> | |
<style> | |
.school { | |
background-color: deeppink; | |
}</style> |
# src-mixin (混入 \ 混合)
mixin (混入) 介绍:
- 功能:可以把多个组件共用的配置提取成一个混入对象
- 使用方式:
- 第一步定义混合:
{
data(){....},
methods:{....}
....
}
- 第二步使用混入:
- 全局混入:
Vue.mixin(xxx)
- 局部混入:
mixins:['xxx']
- 全局混入:
- 第一步定义混合:
import Vue from 'vue' | |
import App from './App.vue' | |
// 导入混合 | |
import {mixin} from './mixin' | |
Vue.config.productionTip = false | |
// 配置全局混合 | |
Vue.mixin(mixin) | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<Student></Student> | |
<School></School> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student,School, | |
}, | |
};</script> | |
<style> | |
</style> |
export const mixin = { | |
methods: { | |
demo() { | |
alert(this.name); | |
} | |
}, | |
} |
<template> | |
<div class="school"> | |
<h2 @click="demo">学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
</div> | |
</template> | |
<script> | |
// 引入一个混合 (局部配置混合) | |
//import { mixin } from "../mixin"; | |
export default { | |
name: "School", | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
// 使用混合 | |
//mixins: [mixin], | |
};</script> | |
<style> | |
.school { | |
background-color: deeppink; | |
}</style> |
<template> | |
<div class="Student"> | |
<h2 @click="demo">学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
</div> | |
</template> | |
<script> | |
// 引入一个混合 (局部配置混合) | |
//import {mixin} from '../mixin' | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "未知", | |
}; | |
}, | |
// 使用混合 | |
//mixins:[mixin] | |
};</script> | |
<style> | |
.Student { | |
background-color: deeppink; | |
}</style> |
# src - 插件
插件介绍:
- 功能:用于增强 Vue
- 本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据。
- 定义插件:
js配置 对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入 (合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
- 使用插件:
Vue.use()
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入插件 | |
import plugin from './plugins' | |
Vue.config.productionTip = false | |
// 应用插件 | |
Vue.use(plugin) | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
export default { | |
install(Vue){//install:定义插件函数 | |
console.log('使用插件成功!',Vue); | |
// 可以配置很多全局方法 | |
} | |
} |
<template> | |
<div> | |
<Student></Student> | |
<School></School> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student,School, | |
}, | |
};</script> | |
<style> | |
</style> |
<template> | |
<div class="school"> | |
<h2>学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "School", | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
};</script> | |
<style> | |
.school { | |
background-color: deeppink; | |
}</style> |
<template> | |
<div class="Student"> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "女", | |
}; | |
}, | |
};</script> | |
<style> | |
.Student { | |
background-color: deeppink; | |
}</style> |
# src_scoped 样式
scoped 样式介绍
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<Student></Student> | |
<School></School> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student, | |
School, | |
}, | |
};</script> | |
<style lang="less"> | |
.demo2 { | |
background-color: rgb(255, 0, 0); | |
.demo3{ | |
font-size: 50px; | |
} | |
}</style> |
<template> | |
<div class="demo2"> | |
<h2 class="demo3">学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "School", | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
};</script> | |
/* scoped:局部限制css名字防止项目整合时出现命名冲突 */ | |
<style scoped> | |
.demo { | |
background-color: rgb(7, 186, 199); | |
}</style> |
<template> | |
<div class="demo"> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "女", | |
}; | |
}, | |
};</script> | |
<style scoped> | |
.demo { | |
background-color: deeppink; | |
}</style> |
# src-TodoList 案例
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader :receive="addTodo"/> | |
<List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> | |
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: [ | |
{ id: "001", title: "吃饭", done: true }, | |
{ id: "002", title: "喝酒", done: false }, | |
{ id: "003", title: "睡觉", done: true }, | |
], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done | |
}); | |
}, | |
}, | |
};</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
}</style> |
<template> | |
<li> | |
<label> | |
<input type="checkbox" :checked="todo.done" @click="itemCheck(todo.id)"/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span>{ {todo.title} }</span> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)">删除</button> | |
</li> | |
</template> | |
<script> | |
export default { | |
name:'Item', | |
// 接收数据 | |
props:['todo','checkTodo','deleteTodo'], | |
methods:{ | |
itemCheck(id){ | |
// 通知 App 组件将对应的 done 属性取反 | |
this.checkTodo(id) | |
}, | |
deleteButton(id){ | |
// 通知 App 组件删除对应的 todo 对象 | |
this.deleteTodo(id) | |
} | |
} | |
}</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover{ | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button{ | |
display: block; | |
}</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
:checkTodo="checkTodo" | |
:deleteTodo="deleteTodo" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos", "checkTodo", "deleteTodo"], | |
};</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
}</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
this.checkAllTodo(checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
this.clearAllTodo() | |
} | |
}, | |
};</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
}</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
props: ["receive"], | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.receive(todoObj); | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
};</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
}</style> |
# 浏览器本地储存
webStorage 介绍
- 存储内容大小一般支持 5MB 左右(不同浏览器可能还不一样)
- 浏览器端通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制。 - 相关 API:
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。xxxxxStorage.clear()
该方法会清空存储中的所有数据。
- 备注:
SessionStorage
存储的内容会随着浏览器窗口关闭而消失。LocalStorage
存储的内容,需要手动清除才会消失。xxxxxStorage.getItem(xxx)
如果 xxx 对应的 value 获取不到,那么getItem
的返回值是null
。JSON.parse(null)
的结果依然是null
。
<!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"> | |
<title>localStorage</title> | |
</head> | |
<body> | |
<h1>localStorage</h1> | |
<button onclick="savaData()">点我保存信息</button> | |
<button onclick="readData()">点我获取信息</button> | |
<button onclick="deleteData()">点我删除数据</button> | |
<button onclick="deleteAllData()">点我清空数据</button> | |
<script> | |
// 本地保存搜索信息 | |
function savaData() { | |
localStorage.setItem('msg', '张三'); | |
localStorage.setItem('msg2', '李四'); | |
} | |
// 读取缓存信息 | |
function readData() { | |
console.log(localStorage.getItem('msg')); | |
} | |
// 删除一条信息 | |
function deleteData() { | |
localStorage.removeItem('msg'); | |
} | |
// 清空 | |
function deleteAllData() { | |
localStorage.clear(); | |
}</script> | |
</body> | |
</html> |
<!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"> | |
<title>sessionStorage</title> | |
</head> | |
<body> | |
<h1>sessionStorage</h1> | |
<button onclick="savaData()">点我保存信息</button> | |
<button onclick="readData()">点我获取信息</button> | |
<button onclick="deleteData()">点我删除数据</button> | |
<button onclick="deleteAllData()">点我清空数据</button> | |
<script> | |
// 本地保存搜索信息 | |
function savaData() { | |
sessionStorage.setItem('msg', '张三'); | |
sessionStorage.setItem('msg2', '李四'); | |
} | |
// 读取缓存信息 | |
function readData() { | |
console.log(sessionStorage.getItem('msg')); | |
} | |
// 删除一条信息 | |
function deleteData() { | |
sessionStorage.removeItem('msg'); | |
} | |
// 清空 | |
function deleteAllData() { | |
sessionStorage.clear(); | |
}</script> | |
</body> | |
</html> |
# src - 完整版 TodoList 案例
总结 TodoList 案例
- 组件化编码流程:
- 拆分静态组件:组件要按照功能点拆分,命名不要与 html 元素冲突。
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可。
- 一些组件在用:放在他们共同的父组件上(
<span style="color:red">
状态提升</span>
)。 - 实现交互:从绑定事件开始。
- props 适用于:
- 父组件 ==> 子组件 通信
- 子组件 ==> 父组件 通信(要求父先给子一个函数)
- 使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!
- props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做。
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader :receive="addTodo"/> | |
<List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> | |
<MyFooter | |
:todos="todos" | |
:checkAllTodo="checkAllTodo" | |
:clearAllTodo="clearAllTodo" | |
/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: JSON.parse(localStorage.getItem("todos")) || [], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done; | |
}); | |
}, | |
}, | |
watch: { | |
todos:{ | |
deep: true, // 深度监视 | |
handler(value) { | |
localStorage.setItem("todos", JSON.stringify(value)); | |
}, | |
}, | |
}, | |
}; | |
</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
</style> |
<template> | |
<li> | |
<label> | |
<input type="checkbox" :checked="todo.done" @click="itemCheck(todo.id)"/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span>{ {todo.title} }</span> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)">删除</button> | |
</li> | |
</template> | |
<script> | |
export default { | |
name:'Item', | |
// 接收数据 | |
props:['todo','checkTodo','deleteTodo'], | |
methods:{ | |
itemCheck(id){ | |
// 通知 App 组件将对应的 done 属性取反 | |
this.checkTodo(id) | |
}, | |
deleteButton(id){ | |
// 通知 App 组件删除对应的 todo 对象 | |
this.deleteTodo(id) | |
} | |
} | |
} | |
</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover{ | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button{ | |
display: block; | |
} | |
</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
:checkTodo="checkTodo" | |
:deleteTodo="deleteTodo" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos", "checkTodo", "deleteTodo"], | |
}; | |
</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
} | |
</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
this.checkAllTodo(checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
this.clearAllTodo() | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
} | |
</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
props: ["receive"], | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim()) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.receive(todoObj); | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
} | |
</style> |
# src - 自定义事件绑定
组件的自定义事件
- 一种组件间通信的方式,适用于:
<strong style="color:red">
子组件 ===> 父组件</strong>
- 使用场景:A 是父组件,B 是子组件,B 想给 A 传数据。
- 那么就要在 A 中给 B 绑定自定义事件(
<span style="color:red">
事件的回调在 A 中</span>
)。 - 绑定自定义事件:
- 第一种方式,在父组件中:
<Demo @atguigu="test"/>
<Demo v-on:atguigu="test"/>
- 第二种方式,在父组件中:
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
- 若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。
- 第一种方式,在父组件中:
- 触发自定义事件:
this.$emit('atguigu',数据)
- 解绑自定义事件
this.$off('atguigu')
- 组件上也可以绑定原生 DOM 事件,需要使用
native
修饰符。 - 注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调<span style="color:red">
要么配置在 methods 中</span>
,<span style="color:red">
要么用箭头函数</span>
,否则 this 指向会出问题!
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<!-- 通过父组件给子组件传递函数类型的 props 实现:子给父传递数据 --> | |
<School :getStudentName="getStudentName"></School> | |
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 (第一种写法 @或 v-on)--> | |
<Student v-on:rain="getRain" @demo="m1"></Student> | |
<!-- 第二种写法 --> | |
<!-- <Student ref="student"></Student> --> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student, | |
School, | |
}, | |
methods: { | |
getStudentName(name) { | |
alert("提交成功:" + name); | |
}, | |
getRain(name) { | |
console.log("getStudent被调用了", name); | |
}, | |
m1(){ | |
console.log("demo被调用了"); | |
} | |
}, | |
mounted(){// App 挂着完毕后调用 mounted 函数 | |
//this.$refs.student.$on ('rain',this.getRain);// 绑定自定义事件 | |
// 只能点击一次 | |
//this.$refs.student.$once ('rain',this.getRain);// 绑定自定义事件 (一次性) | |
// 延迟一秒调用 | |
/* setTimeout(()=>{ | |
this.$refs.student.$on('rain',this.getRain); | |
},1000); */ | |
} | |
};</script> | |
<style lang="less"> | |
.demo2 { | |
background-color: rgb(255, 0, 0); | |
.demo3 { | |
font-size: 50px; | |
} | |
}</style> |
<template> | |
<div class="demo2"> | |
<h2 class="demo3">学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
<button @click="sendStudentNameButton">将学校名字提交给App</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "School", | |
props:['getStudentName'], | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
methods: { | |
sendStudentNameButton() { | |
this.getStudentName(this.name); | |
}, | |
}, | |
}; | |
</script> | |
/* scoped:局部限制css名字防止项目整合时出现命名冲突 */ | |
<style scoped> | |
.demo { | |
background-color: rgb(7, 186, 199); | |
} | |
</style> |
<template> | |
<div class="demo"> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
<button @click="sendStudentNameButton2">自定义事件绑定</button> | |
<button @click="deleteStudentNameButton">解绑自定义事件</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "女", | |
}; | |
}, | |
methods: { | |
sendStudentNameButton2() { | |
// 触发 Student 组件实例身上的 rain 事件 | |
this.$emit("rain", this.name); | |
this.$emit("demo") | |
}, | |
deleteStudentNameButton(){ | |
//this.$off ('rain');// 解绑一个事件 | |
this.$off(['rain','demo']);// 解绑多个事件 | |
this.$off();// 解绑所有自定义事件 | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
.demo { | |
background-color: deeppink; | |
}</style> |
# src_组件自定义事件
// 引入 Vue | |
import Vue from 'vue' | |
// 引入 App | |
import App from './App.vue' | |
// 关闭 Vue 的生产提示 | |
Vue.config.productionTip = false | |
// 创建 vm | |
new Vue({ | |
el:'#app', | |
render: h => h(App), | |
/* mounted() { | |
setTimeout(()=>{ | |
this.$destroy() | |
},3000) | |
}, */ | |
}) |
<template> | |
<div class="app"> | |
<h1>,学生姓名是:</h1> | |
<!-- 通过父组件给子组件传递函数类型的 props 实现:子给父传递数据 --> | |
<School :getSchoolName="getSchoolName"/> | |
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用 @或 v-on) --> | |
<!-- <Student @atguigu="getStudentName" @demo="m1"/> --> | |
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用 ref) --> | |
<Student ref="student" @click.native="show"/> | |
</div> | |
</template> | |
<script> | |
import Student from './components/Student' | |
import School from './components/School' | |
export default { | |
name:'App', | |
components:{School,Student}, | |
data() { | |
return { | |
msg:'你好啊!', | |
studentName:'' | |
} | |
}, | |
methods: { | |
getSchoolName(name){ | |
console.log('App收到了学校名:',name) | |
}, | |
getStudentName(name,...params){ | |
console.log('App收到了学生名:',name,params) | |
this.studentName = name | |
}, | |
m1(){ | |
console.log('demo事件被触发了!') | |
}, | |
show(){ | |
alert(123) | |
} | |
}, | |
mounted() { | |
this.$refs.student.$on('atguigu',this.getStudentName) // 绑定自定义事件 | |
//this.$refs.student.$once ('atguigu',this.getStudentName) // 绑定自定义事件(一次性) | |
}, | |
} | |
</script> | |
<style scoped> | |
.app{ | |
background-color: gray; | |
padding: 5px; | |
} | |
</style> |
<template> | |
<div class="school"> | |
<h2>学校名称:</h2> | |
<h2>学校地址:</h2> | |
<button @click="sendSchoolName">把学校名给App</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'School', | |
props:['getSchoolName'], | |
data() { | |
return { | |
name:'尚硅谷', | |
address:'北京', | |
} | |
}, | |
methods: { | |
sendSchoolName(){ | |
this.getSchoolName(this.name) | |
} | |
}, | |
} | |
</script> | |
<style scoped> | |
.school{ | |
background-color: skyblue; | |
padding: 5px; | |
} | |
</style> |
<template> | |
<div class="student"> | |
<h2>学生姓名:</h2> | |
<h2>学生性别:</h2> | |
<h2>当前求和为:</h2> | |
<button @click="add">点我number++</button> | |
<button @click="sendStudentlName">把学生名给App</button> | |
<button @click="unbind">解绑atguigu事件</button> | |
<button @click="death">销毁当前Student组件的实例(vc)</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Student', | |
data() { | |
return { | |
name:'张三', | |
sex:'男', | |
number:0 | |
} | |
}, | |
methods: { | |
add(){ | |
console.log('add回调被调用了') | |
this.number++ | |
}, | |
sendStudentlName(){ | |
// 触发 Student 组件实例身上的 atguigu 事件 | |
this.$emit('atguigu',this.name,666,888,900) | |
// this.$emit('demo') | |
// this.$emit('click') | |
}, | |
unbind(){ | |
this.$off('atguigu') // 解绑一个自定义事件 | |
//this.$off (['atguigu','demo']) // 解绑多个自定义事件 | |
//this.$off () // 解绑所有的自定义事件 | |
}, | |
death(){ | |
this.$destroy() // 销毁了当前 Student 组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效。 | |
} | |
}, | |
} | |
</script> | |
<style lang="less" scoped> | |
.student{ | |
background-color: pink; | |
padding: 5px; | |
margin-top: 30px; | |
} | |
</style> |
# src-TodoList 自定义事件
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader @receive="addTodo"/> | |
<List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> | |
<MyFooter | |
:todos="todos" | |
@checkAllTodo="checkAllTodo" | |
@clearAllTodo="clearAllTodo" | |
/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: JSON.parse(localStorage.getItem("todos")) || [], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done; | |
}); | |
}, | |
}, | |
watch: { | |
todos:{ | |
deep: true, // 深度监视 | |
handler(value) { | |
localStorage.setItem("todos", JSON.stringify(value)); | |
}, | |
}, | |
}, | |
};</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
}</style> |
<template> | |
<li> | |
<label> | |
<input type="checkbox" :checked="todo.done" @click="itemCheck(todo.id)"/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span>{ {todo.title} }</span> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)">删除</button> | |
</li> | |
</template> | |
<script> | |
export default { | |
name:'Item', | |
// 接收数据 | |
props:['todo','checkTodo','deleteTodo'], | |
methods:{ | |
itemCheck(id){ | |
// 通知 App 组件将对应的 done 属性取反 | |
this.checkTodo(id) | |
}, | |
deleteButton(id){ | |
// 通知 App 组件删除对应的 todo 对象 | |
this.deleteTodo(id) | |
} | |
} | |
} | |
</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover{ | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button{ | |
display: block; | |
} | |
</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
:checkTodo="checkTodo" | |
:deleteTodo="deleteTodo" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos", "checkTodo", "deleteTodo"], | |
}; | |
</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
} | |
</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
//this.checkAllTodo(checked); | |
this.$emit("checkAllTodo", checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
// this.$emit("checkAllTodo", e.target.checked); | |
//this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
//this.clearAllTodo() | |
this.$emit('clearAllTodo') | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
} | |
</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim()) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.$emit('receive',todoObj) | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
} | |
</style> |
# src - 安装全局事件总线
全局事件总线(GlobalEventBus)
- 一种组件间通信的方式,适用于
<span style="color:red">
任意组件间通信</span>
。 - 安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this // 安装全局事件总线,$bus 就是当前应用的 vm
},
......
})
- 使用事件总线:
- 接收数据:
- A 组件想接收数据,则在 A 组件中给
$bus
绑定自定义事件 - 事件的
<span style="color:red">
回调留在 A 组件自身</span>
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
- A 组件想接收数据,则在 A 组件中给
- 提供数据:
this.$bus.$emit('xxxx',数据)
- 接收数据:
- 最好在
beforeDestroy
钩子中,用$off
去解绑<span style="color:red">
当前组件所用到的</span>
事件。
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<School></School> | |
<Student></Student> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student, | |
School, | |
}, | |
}; | |
</script> | |
<style lang="less"> | |
.demo2 { | |
background-color: rgb(255, 0, 0); | |
.demo3 { | |
font-size: 50px; | |
} | |
}</style> |
<template> | |
<div class="demo2"> | |
<h2 class="demo3">学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "School", | |
props: ["getStudentName"], | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
mounted() { | |
this.$bus.$on("hello", (data) => { | |
console.log("我是School,我收到了数据", data); | |
}); | |
}, | |
beforeDestroy() { //beforeDestroy:当前组件销毁之前处理后事 | |
this.$bus.$off("hello"); | |
}, | |
};</script> | |
/* scoped:局部限制css名字防止项目整合时出现命名冲突 */ | |
<style scoped> | |
.demo { | |
background-color: rgb(7, 186, 199); | |
} | |
</style> |
<template> | |
<div class="demo"> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
<button @click="sendStudentName">将学生名字传给School组件</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "女", | |
}; | |
}, | |
mounted() { | |
console.log("Student", this.$bus); | |
}, | |
methods:{ | |
sendStudentName(){ | |
this.$bus.$emit('hello',this.name); | |
} | |
} | |
}; | |
</script> | |
<style scoped> | |
.demo { | |
background-color: deeppink; | |
} | |
</style> |
# src-TodoList 全局事件总线
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader @receive="addTodo"/> | |
<List :todos="todos"/> | |
<MyFooter | |
:todos="todos" | |
@checkAllTodo="checkAllTodo" | |
@clearAllTodo="clearAllTodo" | |
/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: JSON.parse(localStorage.getItem("todos")) || [], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done; | |
}); | |
}, | |
}, | |
watch: { | |
todos: { | |
deep: true, // 深度监视 | |
handler(value) { | |
localStorage.setItem("todos", JSON.stringify(value)); | |
}, | |
}, | |
}, | |
mounted(){ | |
this.$bus.$on('checkTodo',this.checkTodo); | |
this.$bus.$on('deleteTodo',this.deleteTodo); | |
}, | |
beforeDestroy() { //beforeDestroy:当前组件销毁之前处理后事 | |
this.$bus.$off("checkTodo"); | |
this.$bus.$off("deleteTodo"); | |
}, | |
}; | |
</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
</style> |
<template> | |
<li> | |
<label> | |
<input type="checkbox" :checked="todo.done" @click="itemCheck(todo.id)"/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span>{ { todo.title } }</span> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)">删除</button> | |
</li> | |
</template> | |
<script> | |
export default { | |
name: "Item", | |
// 接收数据 | |
props: ["todo"], | |
methods: { | |
itemCheck(id) { | |
// 通知 App 组件将对应的 done 属性取反 | |
this.$bus.$emit("checkTodo", id); | |
}, | |
deleteButton(id) { | |
// 通知 App 组件删除对应的 todo 对象 | |
this.$bus.$emit("deleteTodo", id); | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover { | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button { | |
display: block; | |
} | |
</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos"], | |
}; | |
</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
} | |
</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
//this.checkAllTodo(checked); | |
this.$emit("checkAllTodo", checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
// this.$emit("checkAllTodo", e.target.checked); | |
//this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
//this.clearAllTodo() | |
this.$emit('clearAllTodo') | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
} | |
</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim()) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.$emit('receive',todoObj) | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
} | |
</style> |
# src - 消息订阅与发布
消息订阅与发布 (pubsub)
- 一种组件间通信的方式,适用于
<span style="color:red">
任意组件间通信</span>
。 - 使用步骤:
- 安装
pubsub
:npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A 组件想接收数据,则在 A 组件中订阅消息,订阅的
<span style="color:red">
回调留在 A 组件自身</span>
methods(){
demo(data){......}
}
......
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo) // 订阅消息
}
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在
beforeDestroy
钩子中,用PubSub.unsubscribe(pid)
去<span style="color:red">
取消订阅</span>
- 安装
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
}).$mount('#app') |
<template> | |
<div> | |
<School></School> | |
<Student></Student> | |
</div> | |
</template> | |
<script> | |
import Student from "./components/Student"; | |
import School from "./components/School"; | |
export default { | |
name: "App", | |
components: { | |
Student, | |
School, | |
}, | |
};</script> | |
<style lang="less"> | |
.demo2 { | |
background-color: rgb(255, 0, 0); | |
.demo3 { | |
font-size: 50px; | |
} | |
} | |
</style> |
<template> | |
<div class="demo2"> | |
<h2 class="demo3">学校名称:{ { name } }</h2> | |
<h2>学校地址:{ { access } }</h2> | |
</div> | |
</template> | |
<script> | |
import pubsub from 'pubsub-js' | |
export default { | |
name: "School", | |
props: ["getStudentName"], | |
data() { | |
return { | |
name: "尚硅谷", | |
access: "北京", | |
}; | |
}, | |
mounted() { | |
// 接收数据 | |
this.pubId = pubsub.subscribe("hello",function(msg,value){ | |
console.log("School回调成功执行!",value) | |
}) | |
}, | |
beforeDestroy() { //beforeDestroy:当前组件销毁之前处理后事 | |
//this.$bus.$off("hello"); | |
// 取消订阅 | |
pubsub.unsubscribe(pubId) | |
}, | |
};</script> | |
/* scoped:局部限制css名字防止项目整合时出现命名冲突 */ | |
<style scoped> | |
.demo { | |
background-color: rgb(7, 186, 199); | |
}</style> |
<template> | |
<div class="demo"> | |
<h2>学生姓名:{ { name } }</h2> | |
<h2>学生性别:{ { sex } }</h2> | |
<button @click="sendStudentName">将学生名字传给School组件</button> | |
</div> | |
</template> | |
<script> | |
import pubsub from 'pubsub-js' | |
export default { | |
name: "Student", | |
data() { | |
return { | |
name: "雨", | |
sex: "女", | |
}; | |
}, | |
methods:{ | |
sendStudentName(){ | |
// 发送数据 | |
pubsub.publish('hello',this.name) | |
} | |
} | |
};</script> | |
<style scoped> | |
.demo { | |
background-color: deeppink; | |
}</style> |
# src-TodoList 案例 - nextTick
nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行。
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader @receive="addTodo"/> | |
<List :todos="todos"/> | |
<MyFooter | |
:todos="todos" | |
@checkAllTodo="checkAllTodo" | |
@clearAllTodo="clearAllTodo" | |
/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: JSON.parse(localStorage.getItem("todos")) || [], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done; | |
}); | |
}, | |
updateTodo(id,title) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.title = title | |
console.log("修改成功",id,title); | |
} | |
}); | |
} | |
}, | |
watch: { | |
todos: { | |
deep: true, // 深度监视 | |
handler(value) { | |
localStorage.setItem("todos", JSON.stringify(value)); | |
}, | |
}, | |
}, | |
mounted(){ | |
this.$bus.$on('checkTodo',this.checkTodo); | |
this.$bus.$on('deleteTodo',this.deleteTodo); | |
this.$bus.$on('updateTodo',this.updateTodo); | |
}, | |
beforeDestroy() { //beforeDestroy:当前组件销毁之前处理后事 | |
this.$bus.$off("checkTodo"); | |
this.$bus.$off("deleteTodo"); | |
this.$bus.$off("updateTodo"); | |
}, | |
}; | |
</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn-edit { | |
color: #fff; | |
background-color: #00ff40; | |
border: 1px solid #0fa334; | |
margin: 5px; | |
} | |
.btn-edit:hover { | |
color: #fff; | |
background-color: #00ff40; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
</style> |
<template> | |
<li> | |
<label> | |
<input type="checkbox" :checked="todo.done" @click="itemCheck(todo.id)"/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span v-show="!todo.isEdit">{ { todo.title } }</span> | |
<input | |
v-show="todo.isEdit" | |
type="text" | |
:value="todo.title" | |
@blur="blur(todo, $event)" | |
ref="inputTitle" | |
/> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)">删除</button> | |
<button v-show="!todo.isEdit" class="btn btn-edit" @click="edit(todo)"> | |
编辑 | |
</button> | |
</li> | |
</template> | |
<script> | |
export default { | |
name: "Item", | |
// 接收数据 | |
props: ["todo"], | |
methods: { | |
// 失去焦点回调 (真正执行修改逻辑) | |
blur(todo, e) { | |
todo.isEdit = false; | |
if (!e.target.value.trim()) return alert("输入不能为空!"); | |
this.$bus.$emit("updateTodo", todo.id, e.target.value); | |
}, | |
// 编辑 | |
edit(todo) { | |
// 判断是否有 isEdit 属性 | |
if (todo.hasOwnProperty("isEdit")) { | |
todo.isEdit = true; | |
} else { | |
this.$set(todo, "isEdit", true); | |
} | |
// 方法一 自动获取焦点 | |
/* setTimeout (() => { | |
this.$refs.inputTitle.focus (); //focus ():自动获取焦点 | |
}, 200); */ | |
// 方法二 | |
this.$nextTick(function () { | |
this.$refs.inputTitle.focus(); //focus ():自动获取焦点 | |
}); | |
}, | |
// 勾选和取消勾选 | |
itemCheck(id) { | |
// 通知 App 组件将对应的 done 属性取反 | |
this.$bus.$emit("checkTodo", id); | |
}, | |
// 删除 | |
deleteButton(id) { | |
// 通知 App 组件删除对应的 todo 对象 | |
this.$bus.$emit("deleteTodo", id); | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover { | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button { | |
display: block; | |
} | |
</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos"], | |
}; | |
</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
} | |
</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
//this.checkAllTodo(checked); | |
this.$emit("checkAllTodo", checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
// this.$emit("checkAllTodo", e.target.checked); | |
//this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
//this.clearAllTodo() | |
this.$emit('clearAllTodo') | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
} | |
</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim()) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.$emit('receive',todoObj) | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
} | |
</style> |
# src - 过渡与动画
Vue 封装的过度与动画
- 作用:在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名。
- 图示:
<img src="https://img04.sogoucdn.com/app/a/100520146/5990c1dff7dc7a8fb3b34b4462bd0105" style="width:60%" />
- 写法:
- 准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
- 使用
<transition>
包裹要过度的元素,并配置 name 属性:vue文件 <transition name="hello">
<h1 v-show="isShow"> 你好啊!</h1>
</transition>
- 备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。
- 准备好样式:
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<Test/> | |
<Test2/> | |
<Test3/> | |
</div> | |
</template> | |
<script> | |
import Test from "./components/Test"; | |
import Test2 from "./components/Test2"; | |
import Test3 from "./components/Test3"; | |
export default { | |
name: "App", | |
components: { | |
Test, | |
Test2, | |
Test3, | |
}, | |
}; | |
</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn-edit { | |
color: #fff; | |
background-color: #00ff40; | |
border: 1px solid #0fa334; | |
margin: 5px; | |
} | |
.btn-edit:hover { | |
color: #fff; | |
background-color: #00ff40; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
</style> |
<template> | |
<div> | |
<button @click="isShow = !isShow">显示/隐藏</button> | |
<transition name="msg" :appear="true"> | |
<h2 v-show="isShow">{ { msg } }</h2> | |
</transition> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Test", | |
data() { | |
return { msg: "你好啊", isShow: true }; | |
}, | |
}; | |
</script> | |
<style scoped> | |
h2 { | |
background-color: aqua; | |
} | |
.msg-enter-active { | |
animation: rain 1s; | |
} | |
.msg-leave-active { | |
animation: rain 1s reverse; | |
} | |
@keyframes rain { | |
from { | |
transform: translateX(-100%); | |
} | |
to { | |
transform: translateX(0px); | |
} | |
} | |
</style> |
<template> | |
<div> | |
<button @click="isShow = !isShow">显示/隐藏</button> | |
<transition-group name="msg" :appear="true"> | |
<h2 v-show="!isShow" key="1">{ { msg } }</h2> | |
<h2 v-show="isShow" key="2">雨</h2> | |
</transition-group> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Test", | |
data() { | |
return { msg: "你好啊", isShow: true }; | |
}, | |
}; | |
</script> | |
<style scoped> | |
h2 { | |
background-color: aqua; | |
} | |
/** 来的时候激活 *//** 走的时候激活 */ | |
.msg-enter-active, | |
.msg-leave-active { | |
transition: 1s linear; | |
} | |
/** 进入的起点 */ /** 离开的终点 */ | |
.msg-enter, | |
.msg-leave-to { | |
transform: translateX(-100%); | |
} | |
/** 进入的终点 */ /** 离开的起点 */ | |
.msg-leave-active, | |
.msg-leave { | |
transform: translateX(0); | |
} | |
</style> |
<template> | |
<div> | |
<button @click="isShow = !isShow">显示/隐藏</button> | |
<transition-group name="animate__animated animate__bounce" :appear="true" enter-active-class="animate__swing" | |
leave-active-class="animate__bounceOut"> | |
<h2 v-show="!isShow" key="1">{ { msg } }</h2> | |
<h2 v-show="isShow" key="2">雨</h2> | |
</transition-group> | |
</div> | |
</template> | |
<script> | |
import 'animate.css' | |
export default { | |
name: "Test", | |
data() { | |
return { msg: "你好啊", isShow: true }; | |
}, | |
}; | |
</script> | |
<style scoped> | |
h2 { | |
background-color: aqua; | |
} | |
</style> |
# src-TodoList 动画
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div id="root"> | |
<div class="todo-container"> | |
<div class="todo-wrap"> | |
<MyHeader @receive="addTodo"/> | |
<List :todos="todos"/> | |
<MyFooter | |
:todos="todos" | |
@checkAllTodo="checkAllTodo" | |
@clearAllTodo="clearAllTodo" | |
/> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import MyHeader from "./components/MyHeader"; | |
import List from "./components/List"; | |
import MyFooter from "./components/MyFooter"; | |
export default { | |
name: "App", | |
components: { | |
MyHeader, | |
List, | |
MyFooter, | |
}, | |
data() { | |
return { | |
// 定义数据 | |
todos: JSON.parse(localStorage.getItem("todos")) || [], | |
}; | |
}, | |
methods: { | |
// 添加一个 todo | |
addTodo(val) { | |
this.todos.unshift(val); //unshift:在最前面添加一条数据 | |
}, | |
// 勾选一个 todo or 取消勾选一个 todo | |
checkTodo(id) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.done = !todo.done; | |
console.log("修改成功", id); | |
} | |
}); | |
}, | |
// 根据 id 删除 todo 的一个对象 | |
deleteTodo(id) { | |
if (confirm("确定要删除吗")) { | |
// 使用过滤器过滤掉需要删除的 id 然后重新赋值给 todos | |
this.todos = this.todos.filter((todo) => { | |
return todo.id != id; //confirm 确定和取消的弹窗 | |
}); | |
} | |
}, | |
// 全选和取消全选 | |
checkAllTodo(done) { | |
this.todos.forEach((todo) => { | |
todo.done = done; | |
}); | |
}, | |
// 清除所有已经完成的 todo | |
clearAllTodo() { | |
this.todos = this.todos.filter((todo) => { | |
return !todo.done; | |
}); | |
}, | |
updateTodo(id,title) { | |
this.todos.forEach((todo) => { | |
if (todo.id === id) { | |
todo.title = title | |
console.log("修改成功",id,title); | |
} | |
}); | |
} | |
}, | |
watch: { | |
todos: { | |
deep: true, // 深度监视 | |
handler(value) { | |
localStorage.setItem("todos", JSON.stringify(value)); | |
}, | |
}, | |
}, | |
mounted(){ | |
this.$bus.$on('checkTodo',this.checkTodo); | |
this.$bus.$on('deleteTodo',this.deleteTodo); | |
this.$bus.$on('updateTodo',this.updateTodo); | |
}, | |
beforeDestroy() { //beforeDestroy:当前组件销毁之前处理后事 | |
this.$bus.$off("checkTodo"); | |
this.$bus.$off("deleteTodo"); | |
this.$bus.$off("updateTodo"); | |
}, | |
}; | |
</script> | |
<style lang="css"> | |
/*base*/ | |
body { | |
background: #fff; | |
} | |
.btn { | |
display: inline-block; | |
padding: 4px 12px; | |
margin-bottom: 0; | |
font-size: 14px; | |
line-height: 20px; | |
text-align: center; | |
vertical-align: middle; | |
cursor: pointer; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), | |
0 1px 2px rgba(0, 0, 0, 0.05); | |
border-radius: 4px; | |
} | |
.btn-danger { | |
color: #fff; | |
background-color: #da4f49; | |
border: 1px solid #bd362f; | |
} | |
.btn-danger:hover { | |
color: #fff; | |
background-color: #bd362f; | |
} | |
.btn-edit { | |
color: #fff; | |
background-color: #00ff40; | |
border: 1px solid #0fa334; | |
margin: 5px; | |
} | |
.btn-edit:hover { | |
color: #fff; | |
background-color: #00ff40; | |
} | |
.btn:focus { | |
outline: none; | |
} | |
.todo-container { | |
width: 600px; | |
margin: 0 auto; | |
} | |
.todo-container .todo-wrap { | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
} | |
</style> |
<template> | |
<transition name="animate__animated animate__bounce" appear leave-active-class="animate__flash" | |
enter-active-class="animate__bounce"> | |
<li> | |
<label> | |
<input | |
type="checkbox" | |
:checked="todo.done" | |
@click="itemCheck(todo.id)" | |
/> | |
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了 props --> | |
<!-- <input type="checkbox" v-model="todo.done"/> --> | |
<span v-show="!todo.isEdit">{ { todo.title } }</span> | |
<input | |
v-show="todo.isEdit" | |
type="text" | |
:value="todo.title" | |
@blur="blur(todo, $event)" | |
ref="inputTitle" | |
/> | |
</label> | |
<button class="btn btn-danger" @click="deleteButton(todo.id)"> | |
删除 | |
</button> | |
<button v-show="!todo.isEdit" class="btn btn-edit" @click="edit(todo)"> | |
编辑 | |
</button> | |
</li> | |
</transition> | |
</template> | |
<script> | |
import 'animate.css' | |
export default { | |
name: "Item", | |
// 接收数据 | |
props: ["todo"], | |
methods: { | |
// 失去焦点回调 (真正执行修改逻辑) | |
blur(todo, e) { | |
todo.isEdit = false; | |
if (!e.target.value.trim()) return alert("输入不能为空!"); | |
this.$bus.$emit("updateTodo", todo.id, e.target.value); | |
}, | |
// 编辑 | |
edit(todo) { | |
// 判断是否有 isEdit 属性 | |
if (todo.hasOwnProperty("isEdit")) { | |
todo.isEdit = true; | |
} else { | |
this.$set(todo, "isEdit", true); | |
} | |
// 方法一 自动获取焦点 | |
/* setTimeout (() => { | |
this.$refs.inputTitle.focus (); //focus ():自动获取焦点 | |
}, 200); */ | |
// 方法二 | |
this.$nextTick(function () { | |
this.$refs.inputTitle.focus(); //focus ():自动获取焦点 | |
}); | |
}, | |
// 勾选和取消勾选 | |
itemCheck(id) { | |
// 通知 App 组件将对应的 done 属性取反 | |
this.$bus.$emit("checkTodo", id); | |
}, | |
// 删除 | |
deleteButton(id) { | |
// 通知 App 组件删除对应的 todo 对象 | |
this.$bus.$emit("deleteTodo", id); | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*item*/ | |
li { | |
list-style: none; | |
height: 36px; | |
line-height: 36px; | |
padding: 0 5px; | |
border-bottom: 1px solid #ddd; | |
} | |
li label { | |
float: left; | |
cursor: pointer; | |
} | |
li label li input { | |
vertical-align: middle; | |
margin-right: 6px; | |
position: relative; | |
top: -1px; | |
} | |
li button { | |
float: right; | |
display: none; | |
margin-top: 3px; | |
} | |
li:before { | |
content: initial; | |
} | |
li:last-child { | |
border-bottom: none; | |
} | |
li:hover { | |
background-color: rgb(5, 238, 255); | |
} | |
li:hover button { | |
display: block; | |
} | |
</style> |
<template> | |
<ul class="todo-main"> | |
<Item | |
v-for="todoObj in todos" | |
:key="todoObj.id" | |
:todo="todoObj" | |
/> | |
</ul> | |
</template> | |
<script> | |
import Item from "./Item"; | |
export default { | |
name: "List", | |
components: { | |
Item, | |
}, | |
props: ["todos"], | |
}; | |
</script> | |
<style scoped> | |
/*main*/ | |
.todo-main { | |
margin-left: 0px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding: 0px; | |
} | |
.todo-empty { | |
height: 40px; | |
line-height: 40px; | |
border: 1px solid #ddd; | |
border-radius: 2px; | |
padding-left: 5px; | |
margin-top: 10px; | |
} | |
</style> |
<template> | |
<div class="todo-footer" v-show="total"> | |
<label> | |
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> | |
<!-- <input type="checkbox" @change="checkAll" :checked="isAll" /> --> | |
<input type="checkbox" v-model="isAll"/> | |
</label> | |
<span> | |
<span>已完成{ { doneTotal } }</span> / 全部{ { total } } | |
</span> | |
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "MyFooter", | |
props: ["todos", "checkAllTodo","clearAllTodo"], | |
computed: { | |
total() { | |
return this.todos.length; | |
}, | |
doneTotal() { | |
// 判断 方法一 | |
/* let i = 0; | |
this.todos.forEach(e => { | |
if (e.done) i++; | |
}); | |
return i; */ | |
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); | |
}, | |
isAll: { | |
get() { | |
return this.doneTotal === this.total && this.total > 0; | |
}, | |
set(checked) { | |
//this.checkAllTodo(checked); | |
this.$emit("checkAllTodo", checked); | |
}, | |
}, | |
}, | |
methods: { | |
checkAll(e) { | |
// this.$emit("checkAllTodo", e.target.checked); | |
//this.checkAllTodo(e.target.checked); | |
}, | |
clearAll(){ | |
//this.clearAllTodo() | |
this.$emit('clearAllTodo') | |
} | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*footer*/ | |
.todo-footer { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 6px; | |
margin-top: 5px; | |
} | |
.todo-footer label { | |
display: inline-block; | |
margin-right: 20px; | |
cursor: pointer; | |
} | |
.todo-footer label input { | |
position: relative; | |
top: -1px; | |
vertical-align: middle; | |
margin-right: 5px; | |
} | |
.todo-footer button { | |
float: right; | |
margin-top: 5px; | |
} | |
</style> |
<template> | |
<div class="todo-header"> | |
<input | |
type="text" | |
placeholder="请输入你的任务名称,按回车键确认" | |
v-model="title" | |
@keyup.enter="add" | |
/> | |
</div> | |
</template> | |
<script> | |
import { nanoid } from "nanoid"; | |
export default { | |
name: "MyHeader", | |
data() { | |
return { | |
title: "", | |
}; | |
}, | |
methods: { | |
add() { | |
// 判断输入框是否为空 | |
if (!this.title.trim()) return alert("请输入"); | |
// 将用户输入的包装成一个对象 | |
const todoObj = { id: nanoid(), title: this.title, done: false }; | |
// 通知 App 组件去添加一个 todo 对象 | |
this.$emit('receive',todoObj) | |
// 清空输入框 | |
this.title = ""; | |
}, | |
}, | |
}; | |
</script> | |
<style scoped> | |
/*header*/ | |
.todo-header input { | |
width: 560px; | |
height: 28px; | |
font-size: 14px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
padding: 4px 7px; | |
} | |
.todo-header input:focus { | |
outline: none; | |
border-color: rgba(82, 168, 236, 0.8); | |
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), | |
0 0 8px rgba(82, 168, 236, 0.6); | |
} | |
</style> |
# src - 代理服务
vue 脚手架配置代理 v2 版 方式一
- 在 vue.config.js 中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
- 说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
vue 脚手架配置代理 v2 版 方式二
- 编写 vue.config.js 配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1' 开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2' 开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin 设置为 true 时,服务器收到的请求头中的 host 为:localhost:5000
changeOrigin 设置为 false 时,服务器收到的请求头中的 host 为:localhost:8080
changeOrigin 默认值为 true
*/
- 说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div id="app"> | |
<button @click="getStudents">获取学生信息</button> | |
<button @click="getCars">获取汽车信息</button> | |
</div> | |
</template> | |
<script> | |
import axios from "axios"; | |
export default { | |
name: "App", | |
methods: { | |
getStudents() { | |
axios.get("http://localhost:8080/rain/students").then( | |
(response) => { | |
console.log("请求成功了", response.data); | |
}, | |
(error) => { | |
console.log("请求失败了", error.message); | |
} | |
); | |
}, | |
getCars() { | |
axios.get("http://localhost:8080/demo/cars").then( | |
(response) => { | |
console.log("请求成功了", response.data); | |
}, | |
(error) => { | |
console.log("请求失败了", error.message); | |
} | |
); | |
}, | |
}, | |
}; | |
</script> |
<template> | |
</template> | |
<script> | |
export default { | |
name:'List', | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
</template> | |
<script> | |
export default { | |
name:'Search', | |
} | |
</script> | |
<style> | |
</style> |
# src-GitHub 搜索案例
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div class="container"> | |
<Search></Search> | |
<List></List> | |
</div> | |
</template> | |
<script> | |
import axios from "axios"; | |
import List from "./components/List"; | |
import Search from "./components/Search"; | |
export default { | |
name: "App", | |
components: { List, Search }, | |
methods: {}, | |
};</script> |
<template> | |
<div class="row"> | |
<!-- 展示用户列表 --> | |
<div | |
v-show="info.users.length" | |
class="card" | |
v-for="user in info.users" | |
:key="user.login" | |
> | |
<a :href="user.html_url" target="_blank"> | |
<img :src="user.avatar_url" style="width: 100px"/> | |
</a> | |
<p class="card-text">{ { user.login } }</p> | |
</div> | |
<!-- 展示欢迎词 --> | |
<h1 v-show="info.isFirst">欢迎使用!</h1> | |
<!-- 展示加载中 --> | |
<h1 v-show="info.isLoading">加载中...</h1> | |
<!-- 展示错误信息 --> | |
<h1 v-show="info.errMsg">{ { info.errMsg } }</h1> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "List", | |
data() { | |
return { | |
info: { | |
isFirst: true, | |
isLoading: false, | |
errMsg: "", | |
users: [], | |
}, | |
}; | |
}, | |
mounted() { | |
this.$bus.$on("updataListData", (dataObj) => { | |
this.info = {...this.info,...dataObj}; | |
}); | |
}, | |
}; | |
</script> | |
<style scoped> | |
.album { | |
min-height: 50rem; /* Can be removed; just added for demo purposes */ | |
padding-top: 3rem; | |
padding-bottom: 3rem; | |
background-color: #f7f7f7; | |
} | |
.card { | |
float: left; | |
width: 33.333%; | |
padding: 0.75rem; | |
margin-bottom: 2rem; | |
border: 1px solid #efefef; | |
text-align: center; | |
} | |
.card > img { | |
margin-bottom: 0.75rem; | |
border-radius: 100px; | |
} | |
.card-text { | |
font-size: 85%; | |
} | |
</style> |
<template> | |
<section class="jumbotron"> | |
<h3 class="jumbotron-heading">Search Github Users</h3> | |
<div> | |
<input | |
type="text" | |
placeholder="enter the name you search" v-model="keyWord" | |
/> <button @click="getGitHub">Search</button> | |
</div> | |
</section> | |
</template> | |
<script> | |
import axios from 'axios' | |
export default { | |
name:'Search', | |
data(){ | |
return{ | |
keyWord:'' | |
} | |
}, | |
methods:{ | |
getGitHub(){ | |
// 请求前更新 List 的数据 | |
this.$bus.$emit('updataListData',{isFirst:false,isLoading:true,errMsg:"", users:[]}) | |
axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(result =>{ | |
// 请求成功后更新 List 的数据 | |
this.$bus.$emit('updataListData',{isLoading:false,errMsg:"", users:result.data.items}) | |
},error =>{ | |
// 请求失败后更新 List 的数据 | |
this.$bus.$emit('updataListData',{isLoading:false,errMsg:error.message, users:[]}) | |
}) | |
} | |
}, | |
} | |
</script> | |
<style> | |
</style> |
# 插槽
插槽介绍:
- 作用:让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式
- 适用于
<strong style="color:red">
父组件 ===> 子组件</strong>
。 - 分类:具名插槽、默认插槽、作用域插槽
# src - 具名插槽
具名插槽介绍:
父组件中: | |
<Category> | |
<template slot="center"> | |
<div>html结构1</div> | |
</template> | |
<template v-slot:footer> | |
<div>html结构2</div> | |
</template> | |
</Category> | |
子组件中: | |
<template> | |
<div> | |
<!-- 定义插槽 --> | |
<slot name="center">插槽默认内容...</slot> | |
<slot name="footer">插槽默认内容...</slot> | |
</div> | |
</template> |
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div class="container"> | |
<Category title="美食"> | |
<img slot="conter" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""/> | |
<a slot="footer" href="https://s3.ax1x.com/">点我跳转</a> | |
</Category> | |
<Category title="游戏"> | |
<ul slot="conter"> | |
<li v-for="(g, index) in games" :key="index">{ { g } }</li> | |
</ul> | |
<div slot="footer"> | |
<a>游戏</a> | |
<a>游戏</a> | |
</div> | |
</Category> | |
<Category title="电影"> | |
<video slot="conter" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"/> | |
<div slot="footer"> | |
<a slot="footer">电影</a> | |
</div> | |
</Category> | |
</div> | |
</template> | |
<script> | |
import Category from "./components/Category"; | |
export default { | |
name: "App", | |
components: { Category }, | |
data() { | |
return { | |
foods: ["火锅", "烧烤", "小龙虾"], | |
games: ["红色警戒", "穿越火线", "劲舞团"], | |
films: ["《教父》", "《拆弹专家》", "《你好,李焕英》"], | |
}; | |
}, | |
};</script> | |
<style> | |
.container { | |
display: flex; | |
justify-content: space-around; | |
} | |
img { | |
width: 100%; | |
} | |
video{ | |
width: 60%; | |
}</style> |
<template> | |
<div class="category"> | |
<h3>{ {title} }分类</h3> | |
<!-- 默认插槽 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> | |
<slot name='conter'>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> | |
<slot name='footer'>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Category', | |
props:['title'], | |
}</script> | |
<style scoped> | |
.category{ | |
background-color: aqua; | |
widows: 200px; | |
height: 300px; | |
} | |
h3{ | |
background-color:rgb(0, 81, 255); | |
text-align: center; | |
}</style> |
# src - 默认插槽
默认插槽介绍:
父组件中: | |
<Category> | |
<div>html结构1</div> | |
</Category> | |
子组件中: | |
<template> | |
<div> | |
<!-- 定义插槽 --> | |
<slot>插槽默认内容...</slot> | |
</div> | |
</template> |
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div class="container"> | |
<Category title="美食"> | |
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""/> | |
</Category> | |
<Category title="游戏"> | |
<ul> | |
<li v-for="(g, index) in games" :key="index">{ { g } }</li> | |
</ul> | |
</Category> | |
<Category title="电影"> | |
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"/> | |
</Category> | |
</div> | |
</template> | |
<script> | |
import Category from "./components/Category"; | |
export default { | |
name: "App", | |
components: { Category }, | |
data() { | |
return { | |
foods: ["火锅", "烧烤", "小龙虾"], | |
games: ["红色警戒", "穿越火线", "劲舞团"], | |
films: ["《教父》", "《拆弹专家》", "《你好,李焕英》"], | |
}; | |
}, | |
}; | |
</script> | |
<style scoped> | |
.container { | |
display: flex; | |
justify-content: space-around; | |
} | |
img { | |
width: 100%; | |
} | |
</style> |
<template> | |
<div class="category"> | |
<h3>{ {title} }分类</h3> | |
<!-- 默认插槽 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> | |
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Category', | |
props:['title'], | |
} | |
</script> | |
<style scoped> | |
.category{ | |
background-color: aqua; | |
widows: 200px; | |
height: 300px; | |
} | |
h3{ | |
background-color:rgb(0, 81, 255); | |
text-align: center; | |
} | |
</style> |
# src - 作用域插槽
作用域插槽介绍:
- 理解:
<span style="color:red">
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定</span>
。- games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定。
- 具体编码:
父组件中: | |
<Category> | |
<template scope="scopeData"> | |
<!-- 生成的是 ul 列表 --> | |
<ul> | |
<li v-for="g in scopeData.games" :key="g"></li> | |
</ul> | |
</template> | |
</Category> | |
<Category> | |
<template slot-scope="scopeData"> | |
<!-- 生成的是 h4 标题 --> | |
<h4 v-for="g in scopeData.games" :key="g"></h4> | |
</template> | |
</Category> | |
子组件中: | |
<template> | |
<div> | |
<slot :games="games"></slot> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Category', | |
props:['title'], | |
// 数据在子组件自身 | |
data() { | |
return { | |
games:['红色警戒','穿越火线','劲舞团','超级玛丽'] | |
} | |
}, | |
} | |
</script> |
import Vue from 'vue' | |
import App from './App.vue' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div class="container"> | |
<Category title="游戏"> | |
<template scope="rain"> | |
<ul> | |
<li v-for="(g,index) in rain.games" :key="index">{ {g} }</li> | |
</ul> | |
</template> | |
</Category> | |
<Category title="游戏"> | |
<template scope="rain"> | |
<ol> | |
<li v-for="(g,index) in rain.games" :key="index">{ {g} }</li> | |
</ol> | |
</template> | |
</Category> | |
</div> | |
</template> | |
<script> | |
import Category from "./components/Category"; | |
export default { | |
name: "App", | |
components: { Category }, | |
data() { | |
return { | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
.container { | |
display: flex; | |
justify-content: space-around; | |
} | |
img { | |
width: 100%; | |
} | |
video{ | |
width: 60%; | |
} | |
</style> |
<template> | |
<div class="category"> | |
<h3>{ { title } }分类</h3> | |
<!-- 作用域插槽 --> | |
<slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Category", | |
props: ["title"], | |
data() { | |
return { | |
games: ["红色警戒", "穿越火线", "劲舞团"], | |
}; | |
}, | |
}; | |
</script> | |
<style scoped> | |
.category { | |
background-color: aqua; | |
widows: 200px; | |
height: 300px; | |
} | |
h3 { | |
background-color: rgb(0, 81, 255); | |
text-align: center; | |
} | |
</style> |
# src - 求和案例 mapState-mapGetters
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Store | |
import store from './store/index' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
store:store, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<Count/> | |
</div> | |
</template> | |
<script> | |
import Count from "./components/Count"; | |
export default { | |
name: "App", | |
components: { Count }, | |
data() { | |
return { | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 store 文件夹并创建一个 index.js
// 改文件用于创建 Vuex 中最为核心的 store | |
import Vue from 'vue' | |
// 引入 Vuex | |
import Vuex from 'vuex' | |
// 使用插件 | |
Vue.use(Vuex) | |
// 准备 actions—— 用于响应组件中的动作 | |
const actions = { | |
// 加法 | |
//2. 创建函数 接收 dispatch 传来的数据 | |
/* increment (context, value) { | |
// 将数据交给 mutations 执行数据的修改 | |
context.commit ('increment', value) | |
console.log ('actions 被调用了 ', value) | |
}, | |
// 减法 | |
decrement (context, value) { | |
context.commit ('decrement', value) | |
}, */ | |
// 当前求和为奇数再加 | |
incrementoOdd(context, value) { | |
if (context.state.sum % 2) { | |
context.commit('increment', value) | |
} | |
}, | |
// 等等再加 | |
incrementTime(context, value) { | |
setTimeout(() => { | |
context.commit('increment', value) | |
}, 500); | |
} | |
} | |
// 准备 mutations—— 用于操作数据 (state) | |
const mutations = { | |
// 加法 | |
//3. 创建函数 接收 actions 数据 | |
increment(state, value) { | |
// 修改数据 | |
state.sum += value | |
console.log('mutations被调用了', value) | |
}, | |
// 减法 | |
decrement(state, value) { | |
state.sum -= value | |
}, | |
} | |
// 准备 stat 存数据 | |
const state = { | |
// 当前的和 | |
sum: 0, | |
school:'尚硅谷', | |
subject:'JAVA' | |
} | |
// 准备 getterse—— 用于将 state 中的数据进行加工 | |
const getters = { | |
bigSum(state){ | |
return state.sum*10; | |
} | |
} | |
// 创建并暴露 store | |
export default new Vuex.Store({ | |
actions, mutations, state,getters | |
}) |
<template> | |
<div> | |
<h1>当前求和为:{ { sum } }</h1> | |
<h1>放大十倍的和:{ { bigSum } }</h1> | |
<h1>{ { school } }--{ { subject } }</h1> | |
<select v-model.number="n"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
</select> | |
<button @click="increment">+</button> | |
<button @click="decrement">-</button> | |
<button @click="incrementodd">当前求和为奇数再加</button> | |
<button @click="incrementwait">等一等再加</button> | |
</div> | |
</template> | |
<script> | |
import { mapState, mapGetters } from "vuex"; | |
export default { | |
name: "Count", | |
data() { | |
return { | |
n: 1, | |
}; | |
}, | |
computed: { | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(对象写法) | |
// 方式一 | |
//...mapState({sum:'sum',subject:'subject',school:'school'}) | |
// 方式二 | |
...mapState(["sum", "subject", "school"]), | |
/* ********************************************************************* */ | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(对象写法) | |
// 方式一 | |
//...mapGetters({ bigSum: "bigSum" }), | |
// 方式二 | |
...mapGetters(['bigSum']) | |
}, | |
methods: { | |
// 加法 | |
increment() { | |
//1. 调用 store.dispatch 方法执行加法操作 | |
//this.$store.dispatch("increment", this.n); | |
this.$store.commit("increment", this.n); | |
}, | |
// 减法 | |
decrement() { | |
//this.$store.dispatch("decrement", this.n); | |
this.$store.commit("decrement", this.n); | |
}, | |
// 当前求和为奇数再加 | |
incrementodd() { | |
this.$store.dispatch("incrementoOdd", this.n); | |
}, | |
// 等等再加 | |
incrementwait() { | |
this.$store.dispatch("incrementTime", this.n); | |
}, | |
}, | |
mounted() { | |
console.log(this); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
# src - 求和案例 mapMutations-mapActions
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Store | |
import store from './store/index' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
store:store, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<Count/> | |
</div> | |
</template> | |
<script> | |
import Count from "./components/Count"; | |
export default { | |
name: "App", | |
components: { Count }, | |
data() { | |
return { | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 store 文件夹并创建一个 index.js
// 改文件用于创建 Vuex 中最为核心的 store | |
import Vue from 'vue' | |
// 引入 Vuex | |
import Vuex from 'vuex' | |
// 使用插件 | |
Vue.use(Vuex) | |
// 准备 actions—— 用于响应组件中的动作 | |
const actions = { | |
// 加法 | |
//2. 创建函数 接收 dispatch 传来的数据 | |
/* increment (context, value) { | |
// 将数据交给 mutations 执行数据的修改 | |
context.commit ('increment', value) | |
console.log ('actions 被调用了 ', value) | |
}, | |
// 减法 | |
decrement (context, value) { | |
context.commit ('decrement', value) | |
}, */ | |
// 当前求和为奇数再加 | |
incrementoOdd(context, value) { | |
if (context.state.sum % 2) { | |
context.commit('increment', value) | |
} | |
}, | |
// 等等再加 | |
incrementTime(context, value) { | |
setTimeout(() => { | |
context.commit('increment', value) | |
}, 500); | |
} | |
} | |
// 准备 mutations—— 用于操作数据 (state) | |
const mutations = { | |
// 加法 | |
//3. 创建函数 接收 actions 数据 | |
increment(state, value) { | |
// 修改数据 | |
state.sum += value | |
console.log('mutations被调用了', value) | |
}, | |
// 减法 | |
decrement(state, value) { | |
state.sum -= value | |
}, | |
} | |
// 准备 stat 存数据 | |
const state = { | |
// 当前的和 | |
sum: 0, | |
school:'尚硅谷', | |
subject:'JAVA' | |
} | |
// 准备 getterse—— 用于将 state 中的数据进行加工 | |
const getters = { | |
bigSum(state){ | |
return state.sum*10; | |
} | |
} | |
// 创建并暴露 store | |
export default new Vuex.Store({ | |
actions, mutations, state,getters | |
}) |
<template> | |
<div> | |
<h1>当前求和为:{ { sum } }</h1> | |
<h1>放大十倍的和:{ { bigSum } }</h1> | |
<h1>{ { school } }--{ { subject } }</h1> | |
<select v-model.number="n"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
</select> | |
<button @click="increment(n)">+</button> | |
<button @click="decrement(n)">-</button> | |
<button @click="incrementoOdd(n)">当前求和为奇数再加</button> | |
<button @click="incrementTime(n)">等一等再加</button> | |
</div> | |
</template> | |
<script> | |
import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; | |
export default { | |
name: "Count", | |
data() { | |
return { | |
n: 1, | |
}; | |
}, | |
computed: { | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(对象写法) | |
// 方式一 | |
//...mapState({sum:'sum',subject:'subject',school:'school'}) | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(数组写法) | |
// 方式二 | |
...mapState(["sum", "subject", "school"]), | |
/* ********************************************************************* */ | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(对象写法) | |
// 方式一 | |
//...mapGetters({ bigSum: "bigSum" }), | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(数组写法) | |
// 方式二 | |
...mapGetters(["bigSum"]), | |
}, | |
methods: { | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式一 | |
//...mapMutations({ increment: "increment", decrement: "decrement" }), | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式二 | |
...mapMutations(["increment", "decrement"]), | |
/* ********************************************************************** */ | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(对象写法) | |
// 方式一 | |
//...mapActions({incrementodd:'incrementoOdd',incrementwait:'incrementTime'}) | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(数组写法) | |
// 方法二 | |
...mapActions(["incrementoOdd", "incrementTime"]), | |
}, | |
mounted() { | |
console.log(this); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
# src - 组件共享数据
组件共享数据需要使用 Vuex 插件
# 案例代码
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Store | |
import store from './store/index' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
store:store, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<Count/> | |
<Person/> | |
</div> | |
</template> | |
<script> | |
import Count from "./components/Count"; | |
import Person from "./components/Person"; | |
export default { | |
name: "App", | |
components: { Count,Person }, | |
data() { | |
return { | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 store 文件夹创建一个 index.js
// 改文件用于创建 Vuex 中最为核心的 store | |
import Vue from 'vue' | |
// 引入 Vuex | |
import Vuex from 'vuex' | |
// 使用插件 | |
Vue.use(Vuex) | |
// 准备 actions—— 用于响应组件中的动作 | |
const actions = { | |
// 当前求和为奇数再加 | |
incrementoOdd(context, value) { | |
if (context.state.sum % 2) { | |
context.commit('increment', value) | |
} | |
}, | |
// 等等再加 | |
incrementTime(context, value) { | |
setTimeout(() => { | |
context.commit('increment', value) | |
}, 500); | |
}, | |
personList(context, value) { | |
console.log('#######', value, this) | |
context.commit('personList', value) | |
} | |
} | |
// 准备 mutations—— 用于操作数据 (state) | |
const mutations = { | |
// 加法 | |
//3. 创建函数 接收 actions 数据 | |
increment(state, value) { | |
// 修改数据 | |
state.sum += value | |
console.log('mutations被调用了', value) | |
}, | |
// 减法 | |
decrement(state, value) { | |
state.sum -= value | |
}, | |
personList(state, value) { | |
console.log('personList', value) | |
state.personList.unshift(value) | |
} | |
} | |
// 准备 stat 存数据 | |
const state = { | |
// 当前的和 | |
sum: 0, | |
school: '尚硅谷', | |
subject: 'JAVA', | |
personList: [ | |
{ id: '01', name: '雨' } | |
], | |
} | |
// 准备 getterse—— 用于将 state 中的数据进行加工 | |
const getters = { | |
bigSum(state) { | |
return state.sum * 10; | |
} | |
} | |
// 创建并暴露 store | |
export default new Vuex.Store({ | |
actions, mutations, state, getters | |
}) |
<template> | |
<div> | |
<h1>当前求和为:{ { sum } }</h1> | |
<h1> | |
<ul> | |
姓名: | |
<li v-for="personLists in personList" :key="personLists.id"> | |
{ { personLists.name } } | |
</li> | |
</ul> | |
<h1 style="color: red">下方组件总人数:{ { personList.length } }</h1> | |
</h1> | |
<h1>放大十倍的和:{ { bigSum } }</h1> | |
<h1>{ { school } }--{ { subject } }</h1> | |
<select v-model.number="n"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
</select> | |
<button @click="increment(n)">+</button> | |
<button @click="decrement(n)">-</button> | |
<button @click="incrementoOdd(n)">当前求和为奇数再加</button> | |
<button @click="incrementTime(n)">等一等再加</button> | |
</div> | |
</template> | |
<script> | |
import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; | |
export default { | |
name: "Count", | |
data() { | |
return { | |
n: 1, | |
}; | |
}, | |
computed: { | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(对象写法) | |
// 方式一 | |
//...mapState({sum:'sum',subject:'subject',school:'school'}) | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(数组写法) | |
// 方式二 | |
...mapState(["sum", "subject", "school", "personList"]), | |
/* ********************************************************************* */ | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(对象写法) | |
// 方式一 | |
//...mapGetters({ bigSum: "bigSum" }), | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(数组写法) | |
// 方式二 | |
...mapGetters(["bigSum"]), | |
}, | |
methods: { | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式一 | |
//...mapMutations({ increment: "increment", decrement: "decrement" }), | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式二 | |
...mapMutations(["increment", "decrement"]), | |
/* ********************************************************************** */ | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(对象写法) | |
// 方式一 | |
//...mapActions({incrementodd:'incrementoOdd',incrementwait:'incrementTime'}) | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(数组写法) | |
// 方法二 | |
...mapActions(["incrementoOdd", "incrementTime"]), | |
}, | |
mounted() { | |
console.log(this); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h1>人员列表</h1> | |
<input type="text" placeholder="请输入名字" v-model="name"/> | |
<button @click="add">添加</button> | |
<ul> | |
<li v-for="person in $store.state.personList" :key="person.id">{ {person.name} }</li> | |
</ul> | |
</div> | |
</template> | |
<script> | |
import {nanoid} from 'nanoid'; | |
export default { | |
name:'Person', | |
data(){ | |
return{ | |
name:'' | |
} | |
}, | |
methods:{ | |
add(){ | |
const p = {id:nanoid(),name:this.name} | |
this.name='' | |
this.$store.dispatch('personList',p); | |
} | |
} | |
} | |
</script> | |
<style> | |
</style> |
# src - 模块化数据共享
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Store | |
import store from './store/index' | |
Vue.config.productionTip = false | |
new Vue({ | |
render: h => h(App), | |
store:store, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<Count/> | |
<hr> | |
<hr> | |
<hr> | |
<Person/> | |
</div> | |
</template> | |
<script> | |
import Count from "./components/Count"; | |
import Person from "./components/Person"; | |
export default { | |
name: "App", | |
components: { Count, Person}, | |
data() { | |
return { | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 store 文件夹创建一个 index.js、peiqi.js、qiaozhi.js
// 改文件用于创建 Vuex 中最为核心的 store | |
import Vue from 'vue' | |
// 引入 Vuex | |
import Vuex from 'vuex' | |
import peiqi from './peiqi' | |
import qiaozhi from './qiaozhi' | |
// 使用插件 | |
Vue.use(Vuex) | |
// 创建并暴露 store | |
export default new Vuex.Store({ | |
// 开启模块化分类 | |
modules: { | |
countAbout: peiqi, | |
personAbout: qiaozhi | |
} | |
}) |
// 求和功能相关的配置 | |
export default { | |
namespaced: true,// 开启命名空间 | |
actions: { // 当前求和为奇数再加 | |
incrementoOdd(context, value) { | |
if (context.state.sum % 2) { | |
context.commit('increment', value) | |
} | |
}, | |
// 等等再加 | |
incrementTime(context, value) { | |
setTimeout(() => { | |
context.commit('increment', value) | |
}, 500); | |
}, | |
}, | |
mutations: { // 加法 | |
//3. 创建函数 接收 actions 数据 | |
increment(state, value) { | |
// 修改数据 | |
state.sum += value | |
console.log('mutations被调用了', value) | |
}, | |
// 减法 | |
decrement(state, value) { | |
state.sum -= value | |
}, | |
}, | |
state: {// 当前的和 | |
sum: 0, | |
school: '尚硅谷', | |
subject: 'JAVA', | |
}, | |
getters: { | |
bigSum(state) { | |
return state.sum * 10; | |
} | |
}, | |
} |
// 人员管理功能相关的配置 | |
import axios from 'axios'; | |
import { nanoid } from 'nanoid'; | |
export default { | |
namespaced: true,// 开启命名空间 | |
actions: { | |
personList(context, value) { | |
console.log('#######', value, this) | |
if (value.name.indexOf('雨') === 0) { | |
context.commit('personList', value) | |
} else { | |
alert('第一个字必须是雨') | |
} | |
}, | |
// 发送 AJAX 请求 | |
addServer(context) { | |
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(response => { | |
context.commit('personList', { id: nanoid(), name: response.data }) | |
}, err => { | |
alert('获取数据失败了!'+err.message) | |
}) | |
} | |
}, | |
mutations: { | |
personList(state, value) { | |
console.log('personList', value) | |
state.personList.unshift(value) | |
} | |
}, | |
state: { | |
personList: [ | |
{ id: '01', name: '雨' } | |
], | |
}, | |
getters: { | |
firstPersonName(state) { | |
return state.personList[0].name | |
} | |
}, | |
} |
<template> | |
<div> | |
<h1>当前求和为:{ { sum } }</h1> | |
<h1> | |
<ul> | |
姓名: | |
<li v-for="personLists in personList" :key="personLists.id"> | |
{ { personLists.name } } | |
</li> | |
</ul> | |
<h1 style="color: red">下方组件总人数:{ { personList.length } }</h1> | |
</h1> | |
<h1>放大十倍的和:{ { bigSum } }</h1> | |
<h1>{ { school } }--{ { subject } }</h1> | |
<select v-model.number="n"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
</select> | |
<button @click="increment(n)">+</button> | |
<button @click="decrement(n)">-</button> | |
<button @click="incrementoOdd(n)">当前求和为奇数再加</button> | |
<button @click="incrementTime(n)">等一等再加</button> | |
</div> | |
</template> | |
<script> | |
import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; | |
export default { | |
name: "Count", | |
data() { | |
return { | |
n: 1, | |
}; | |
}, | |
computed: { | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(对象写法) | |
// 方式一 | |
//...mapState({sum:'sum',subject:'subject',school:'school'}) | |
// 借助 mapState 生成计算属性,从 state 中读取数据。(数组写法) | |
// 方式二 | |
...mapState('countAbout',["sum", "subject", "school"]), | |
...mapState('personAbout',["personList"]), | |
/* ********************************************************************* */ | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(对象写法) | |
// 方式一 | |
//...mapGetters({ bigSum: "bigSum" }), | |
// 借助 mapGetters 生成计算属性,从 getters 中读取数据。(数组写法) | |
// 方式二 | |
...mapGetters('countAbout',["bigSum"]), | |
}, | |
methods: { | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式一 | |
//...mapMutations({ increment: "increment", decrement: "decrement" }), | |
// 借助 mapMutations 生成对应的方法,从 mutations 中读取数据。(对象写法) | |
// 方式二 | |
...mapMutations('countAbout',["increment", "decrement"]), | |
/* ********************************************************************** */ | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(对象写法) | |
// 方式一 | |
//...mapActions({incrementodd:'incrementoOdd',incrementwait:'incrementTime'}) | |
// 借助 mapActions 生成对应的方法,从 actions 中读取数据。(数组写法) | |
// 方法二 | |
...mapActions('countAbout',["incrementoOdd", "incrementTime"]), | |
}, | |
mounted() { | |
console.log(this); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h1>人员列表</h1> | |
<h2>列表中的第一个人的名字是:{ {firstPersonName} }</h2> | |
<input type="text" placeholder="请输入名字" v-model="name"/> | |
<button @click="add">添加</button> | |
<button @click="addserver">发送axios请求</button> | |
<ul> | |
<li v-for="person in $store.state.personAbout.personList" :key="person.id">{ {person.name} }</li> | |
</ul> | |
</div> | |
</template> | |
<script> | |
import {nanoid} from 'nanoid'; | |
export default { | |
name:'Person', | |
data(){ | |
return{ | |
name:'' | |
} | |
}, | |
computed:{ | |
firstPersonName(){ | |
return this.$store.getters['personAbout/firstPersonName'] | |
} | |
}, | |
methods:{ | |
add(){ | |
console.log(this) | |
const p = {id:nanoid(),name:this.name} | |
this.name='' | |
this.$store.dispatch('personAbout/personList',p); | |
}, | |
addserver(){ | |
this.$store.dispatch('personAbout/addServer') | |
} | |
} | |
} | |
</script> | |
<style> | |
</style> |
# src - 路由基本使用
路由介绍及使用
# 案例代码
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from './components/Banner.vue' | |
export default { | |
name: "App", | |
components:{ | |
Banner | |
} | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 放置路由组件配置
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
routes: [ | |
{ | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<h2>我是Home的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'Home' | |
} | |
</script> | |
<style> | |
</style> |
下面放在 components 文件夹下
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"><h2>Vue Router Demo</h2></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner' | |
}; | |
</script> | |
<style> | |
</style> |
# src - 二级路由
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home, | |
children: [ | |
// 耳机路由配置 | |
{ | |
path: 'news', | |
component: News, | |
}, | |
{ | |
path: 'message', | |
component: Message, | |
} | |
] | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<router-view></router-view> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li><a href="/message1">message001</a> </li> | |
<li><a href="/message2">message002</a> </li> | |
<li><a href="/message/3">message003</a> </li> | |
</ul> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>news001</li> | |
<li>news002</li> | |
<li>news003</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
}; | |
</script> | |
<style> | |
</style> |
下面的放在 components
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"><h2>Vue Router Demo</h2></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner' | |
}; | |
</script> | |
<style> | |
</style> |
# src - 路由的 query 参数和三级路由
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home, | |
children: [ | |
// 耳机路由配置 | |
{ | |
path: 'news', | |
component: News, | |
}, | |
{ | |
path: 'message', | |
component: Message, | |
// 三级路由配置 | |
children:[ | |
{ | |
path: 'detail', | |
component:Detail | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {$route.query.id} }</li> | |
<li>消息标题:{ {$route.query.title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<router-view></router-view> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<!-- 跳转路由并携带 query 参数,to 的字符串写法 --> | |
<!-- <router-link | |
:to="`/home/message/detail?id=${me.id}&title=${me.title}`" | |
>{ { me.title } }</router-link | |
> --> | |
<!-- 跳转路由并携带 query 参数,to 的对象写法 --> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>news001</li> | |
<li>news002</li> | |
<li>news003</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
}; | |
</script> | |
<style> | |
</style> |
下面放在 components 文件夹下
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"><h2>Vue Router Demo</h2></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner' | |
}; | |
</script> | |
<style> | |
</style> |
# src - 从 query 中读取数据简化版
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
name: 'guanyu', | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home, | |
children: [ | |
// 耳机路由配置 | |
{ | |
path: 'news', | |
component: News, | |
}, | |
{ | |
path: 'message', | |
component: Message, | |
// 三级路由配置 | |
children:[ | |
{ | |
name: 'xiangqing', | |
path: 'detail', | |
component:Detail, | |
// 第三种写法 常用 值为函数 | |
props($route){ | |
return {id:$route.query.id,title:$route.query.title} | |
} | |
/* props (query){// 解构赋值 | |
return {id:query.id,title:query.title} | |
} */ | |
/* props ({query:{id,title} }){// 连续解构赋值 | |
return {id,title} | |
} */ | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id', 'title'], | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<router-view></router-view> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
<!-- 跳转路由并携带 params 参数,to 的字符串写法 --> | |
<!-- <router-link | |
:to="`/home/message/detail/${me.id}/${me.title}`" | |
>{ { me.title } }</router-link | |
> --> | |
<!-- 跳转路由并携带 params 参数,to 的对象写法,不能使用 Path 路径 --> | |
<!-- <router-link | |
:to="{ | |
name:'xiangqing', | |
params:{ | |
id:me.id,title:me.title | |
} | |
}" | |
>{ { me.title } }</router-link | |
> --> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>news001</li> | |
<li>news002</li> | |
<li>news003</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
}; | |
</script> | |
<style> | |
</style> |
下面的放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"><h2>Vue Router Demo</h2></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner' | |
}; | |
</script> | |
<style> | |
</style> |
# src - 路由缓存
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
name: 'guanyu', | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home, | |
children: [ | |
// 耳机路由配置 | |
{ | |
path: 'news', | |
component: News, | |
}, | |
{ | |
path: 'message', | |
component: Message, | |
// 三级路由配置 | |
children:[ | |
{ | |
name: 'xiangqing', | |
path: 'detail', | |
component:Detail, | |
// 第三种写法 常用 值为函数 | |
props($route){ | |
return {id:$route.query.id,title:$route.query.title} | |
} | |
/* props (query){// 解构赋值 | |
return {id:query.id,title:query.title} | |
} */ | |
/* props ({query:{id,title} }){// 连续解构赋值 | |
return {id,title} | |
} */ | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id', 'title'], | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<!-- 路由缓存标签 keep-alive include: 指定需要缓存的组件名字 或者写成数组 --> | |
<!-- <keep-alive include='News'> | |
<router-view></router-view> | |
</keep-alive> --> | |
<!-- ------------------------------------------------------------------------ --> | |
<!-- 多组件路由缓存 --> | |
<keep-alive :include="['News', 'Message']"> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
<button @click='pushShow(me)'>push查看</button> | |
<button @click='replaceShow(me)'>replace查看</button> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
beforeDestroy(){ | |
console.log('Message即将被销毁') | |
}, | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
methods:{ | |
pushShow(me){ | |
this.$router.push({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
}, | |
replaceShow(me){ | |
this.$router.replace({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
} | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>news001<input type="text"></li> | |
<li>news002<input type="text"></li> | |
<li>news003<input type="text"></li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
beforeDestroy(){ | |
console.log('News即将被销毁') | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
下面的放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"> | |
<h2>Vue Router Demo</h2> | |
<button @click="back">前进</button> | |
<button @click="forward">后退</button> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner', | |
methods:{ | |
back(){ | |
this.$router.back(); | |
}, | |
forward(){ | |
this.$router.forward(); | |
} | |
} | |
}; | |
</script> | |
<style> | |
</style> |
# src - 路由生命周期钩子
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
export default new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
name: 'guanyu', | |
path: '/about', | |
component: Aboout | |
}, | |
{ | |
path: '/home', | |
component: Home, | |
children: [ | |
// 耳机路由配置 | |
{ | |
path: 'news', | |
component: News, | |
}, | |
{ | |
path: 'message', | |
component: Message, | |
// 三级路由配置 | |
children:[ | |
{ | |
name: 'xiangqing', | |
path: 'detail', | |
component:Detail, | |
// 第三种写法 常用 值为函数 | |
props($route){ | |
return {id:$route.query.id,title:$route.query.title} | |
} | |
/* props (query){// 解构赋值 | |
return {id:query.id,title:query.title} | |
} */ | |
/* props ({query:{id,title} }){// 连续解构赋值 | |
return {id,title} | |
} */ | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id', 'title'], | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<!-- 路由缓存标签 keep-alive include: 指定需要缓存的组件名字 或者写成数组 --> | |
<!-- <keep-alive include='News'> | |
<router-view></router-view> | |
</keep-alive> --> | |
<!-- ------------------------------------------------------------------------ --> | |
<!-- 多组件路由缓存 --> | |
<keep-alive :include="['News', 'Message']"> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
<button @click='pushShow(me)'>push查看</button> | |
<button @click='replaceShow(me)'>replace查看</button> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
beforeDestroy(){ | |
console.log('Message即将被销毁') | |
}, | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
methods:{ | |
pushShow(me){ | |
this.$router.push({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
}, | |
replaceShow(me){ | |
this.$router.replace({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
} | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li :style="{ opacity }">欢迎学习Vue</li> | |
<li>news001<input type="text"/></li> | |
<li>news002<input type="text"/></li> | |
<li>news003<input type="text"/></li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
data() { | |
return { | |
opacity: 1, | |
}; | |
}, | |
beforeDestroy() { | |
console.log("News即将被销毁"); | |
}, | |
activated() { | |
console.log("News被激活了"); | |
this.timer = setInterval(() => { | |
console.log("@"); | |
this.opacity -= 0.01; | |
if (this.opacity <= 0) this.opacity = 1; | |
}, 16); | |
}, | |
deactivated() { | |
console.log("News失活了"); | |
clearInterval(this.timer); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
下面放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"> | |
<h2>Vue Router Demo</h2> | |
<button @click="back">前进</button> | |
<button @click="forward">后退</button> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner', | |
methods:{ | |
back(){ | |
this.$router.back(); | |
}, | |
forward(){ | |
this.$router.forward(); | |
} | |
} | |
}; | |
</script> | |
<style> | |
</style> |
# src_两个新的生命周期钩子
// 引入 Vue | |
import Vue from 'vue' | |
// 引入 App | |
import App from './App.vue' | |
// 引入 VueRouter | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router' | |
// 关闭 Vue 的生产提示 | |
Vue.config.productionTip = false | |
// 应用插件 | |
Vue.use(VueRouter) | |
// 创建 vm | |
new Vue({ | |
el:'#app', | |
render: h => h(App), | |
router:router | |
}) |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- 原始 html 中我们使用 a 标签实现页面的跳转 --> | |
<!-- <a class="list-group-item active" href="./about.html">About</a> --> | |
<!-- <a class="list-group-item" href="./home.html">Home</a> --> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link class="list-group-item" active-class="active" to="/about">About</router-link> | |
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from './components/Banner' | |
export default { | |
name:'App', | |
components:{Banner} | |
} | |
</script> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件 | |
import About from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建并暴露一个路由器 | |
export default new VueRouter({ | |
routes:[ | |
{ | |
name:'guanyu', | |
path:'/about', | |
component:About | |
}, | |
{ | |
path:'/home', | |
component:Home, | |
children:[ | |
{ | |
path:'news', | |
component:News, | |
}, | |
{ | |
path:'message', | |
component:Message, | |
children:[ | |
{ | |
name:'xiangqing', | |
path:'detail', | |
component:Detail, | |
//props 的第一种写法,值为对象,该对象中的所有 key-value 都会以 props 的形式传给 Detail 组件。 | |
// props:{a:1,b:'hello'} | |
//props 的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有 params 参数,以 props 的形式传给 Detail 组件。 | |
// props:true | |
//props 的第三种写法,值为函数 | |
props($route){ | |
return { | |
id:$route.query.id, | |
title:$route.query.title, | |
a:1, | |
b:'hello' | |
} | |
} | |
} | |
] | |
} | |
] | |
} | |
] | |
}) |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About', | |
/* beforeDestroy () { | |
console.log ('About 组件即将被销毁了 ') | |
},*/ | |
/* mounted () { | |
console.log ('About 组件挂载完毕了 ',this) | |
window.aboutRoute = this.$route | |
window.aboutRouter = this.$router | |
}, */ | |
} | |
</script> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id','title'], | |
computed: { | |
// id(){ | |
// return this.$route.query.id | |
// }, | |
// title(){ | |
// return this.$route.query.title | |
// }, | |
}, | |
mounted() { | |
// console.log(this.$route) | |
}, | |
} | |
</script> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> | |
</li> | |
<li> | |
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> | |
</li> | |
</ul> | |
<keep-alive include="News"> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Home', | |
/* beforeDestroy () { | |
console.log ('Home 组件即将被销毁了 ') | |
}, */ | |
/* mounted () { | |
console.log ('Home 组件挂载完毕了 ',this) | |
window.homeRoute = this.$route | |
window.homeRouter = this.$router | |
}, */ | |
} | |
</script> |
<template> | |
<div> | |
<ul> | |
<li v-for="m in messageList" :key="m.id"> | |
<!-- 跳转路由并携带 params 参数,to 的字符串写法 --> | |
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{ {m.title} }</router-link> --> | |
<!-- 跳转路由并携带 params 参数,to 的对象写法 --> | |
<router-link :to="{ | |
name:'xiangqing', | |
query:{ | |
id:m.id, | |
title:m.title | |
} | |
}"> | |
{ {m.title} } | |
</router-link> | |
<button @click="pushShow(m)">push查看</button> | |
<button @click="replaceShow(m)">replace查看</button> | |
</li> | |
</ul> | |
<hr> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Message', | |
data() { | |
return { | |
messageList:[ | |
{id:'001',title:'消息001'}, | |
{id:'002',title:'消息002'}, | |
{id:'003',title:'消息003'} | |
] | |
} | |
}, | |
methods: { | |
pushShow(m){ | |
this.$router.push({ | |
name:'xiangqing', | |
query:{ | |
id:m.id, | |
title:m.title | |
} | |
}) | |
}, | |
replaceShow(m){ | |
this.$router.replace({ | |
name:'xiangqing', | |
query:{ | |
id:m.id, | |
title:m.title | |
} | |
}) | |
} | |
}, | |
beforeDestroy() { | |
//console.log ('Message 组件即将被销毁了 ') | |
}, | |
} | |
</script> |
<template> | |
<ul> | |
<li :style="{opacity}">欢迎学习Vue</li> | |
<li>news001 <input type="text"></li> | |
<li>news002 <input type="text"></li> | |
<li>news003 <input type="text"></li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'News', | |
data() { | |
return { | |
opacity:1 | |
} | |
}, | |
/* beforeDestroy () { | |
console.log ('News 组件即将被销毁了 ') | |
clearInterval (this.timer) | |
}, */ | |
/* mounted(){ | |
this.timer = setInterval(() => { | |
console.log('@') | |
this.opacity -= 0.01 | |
if(this.opacity <= 0) this.opacity = 1 | |
},16) | |
}, */ | |
activated() { | |
console.log('News组件被激活了') | |
this.timer = setInterval(() => { | |
console.log('@') | |
this.opacity -= 0.01 | |
if(this.opacity <= 0) this.opacity = 1 | |
},16) | |
}, | |
deactivated() { | |
console.log('News组件失活了') | |
clearInterval(this.timer) | |
}, | |
} | |
</script> |
下面放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"> | |
<h2>Vue Router Demo</h2> | |
<button @click="back">后退</button> | |
<button @click="forward">前进</button> | |
<button @click="test">测试一下go</button> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner', | |
methods: { | |
back(){ | |
this.$router.back() | |
// console.log(this.$router) | |
}, | |
forward(){ | |
this.$router.forward() | |
}, | |
test(){ | |
this.$router.go(3) | |
} | |
}, | |
} | |
</script> |
# src - 全局路由守卫
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件夹
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
const router = new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
name: 'guanyu', | |
path: '/about', | |
component: Aboout, | |
meta:{title:'关于'}, | |
}, | |
{ | |
name: 'zhouye', | |
path: '/home', | |
component: Home, | |
meta:{title:'主页'}, | |
children: [ | |
// 耳机路由配置 | |
{ | |
name:'xinwen', | |
path: 'news', | |
component: News, | |
meta:{isAuth: true,title:'新闻'} | |
}, | |
{ | |
name: 'xiaoxi', | |
path: 'message', | |
component: Message, | |
meta:{isAuth: true,title:'消息'}, | |
// 三级路由配置 | |
children: [ | |
{ | |
name: 'xiangqing', | |
path: 'detail', | |
component: Detail, | |
meta:{isAuth: true,title:'详情'}, | |
// 第三种写法 常用 值为函数 | |
props($route) { | |
return { id: $route.query.id, title: $route.query.title } | |
} | |
/* props (query){// 解构赋值 | |
return {id:query.id,title:query.title} | |
} */ | |
/* props ({query:{id,title} }){// 连续解构赋值 | |
return {id,title} | |
} */ | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); | |
// 适用于判断是否登陆 | |
// 全局前置路由守卫 —— 初始化的时候被调用、每次路由切换之前被调用 | |
router.beforeEach((to, from, next) => { | |
//console.log(to, from, next); | |
console.log(to.meta.isAuth); | |
if (to.meta.isAuth) { | |
//localStorage.getItem ():获取当前 cooker 里面的 school | |
if (localStorage.getItem('school') === 'rain') { | |
next()// 放行 | |
}else{ | |
alert('cooke信息不对') | |
} | |
}else{ | |
next() | |
} | |
}) | |
// 全局后置路由守卫 —— 初始化的时候被调用、每次路由切换之后被调用 | |
router.afterEach((to, from)=>{ | |
console.log('后置路由守卫',to,from) | |
document.title = to.meta.title || '硅谷系统' | |
}) | |
export default router |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id', 'title'], | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<!-- 路由缓存标签 keep-alive include: 指定需要缓存的组件名字 或者写成数组 --> | |
<!-- <keep-alive include='News'> | |
<router-view></router-view> | |
</keep-alive> --> | |
<!-- ------------------------------------------------------------------------ --> | |
<!-- 多组件路由缓存 --> | |
<keep-alive :include="['News', 'Message']"> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
<button @click='pushShow(me)'>push查看</button> | |
<button @click='replaceShow(me)'>replace查看</button> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
beforeDestroy(){ | |
console.log('Message即将被销毁') | |
}, | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
methods:{ | |
pushShow(me){ | |
this.$router.push({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
}, | |
replaceShow(me){ | |
this.$router.replace({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
} | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li :style="{ opacity }">欢迎学习Vue</li> | |
<li>news001<input type="text"/></li> | |
<li>news002<input type="text"/></li> | |
<li>news003<input type="text"/></li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
data() { | |
return { | |
opacity: 1, | |
}; | |
}, | |
beforeDestroy() { | |
console.log("News即将被销毁"); | |
}, | |
activated() { | |
console.log("News被激活了"); | |
this.timer = setInterval(() => { | |
console.log("@"); | |
this.opacity -= 0.01; | |
if (this.opacity <= 0) this.opacity = 1; | |
}, 16); | |
}, | |
deactivated() { | |
console.log("News失活了"); | |
clearInterval(this.timer); | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
下面的放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"> | |
<h2>Vue Router Demo</h2> | |
<button @click="back">前进</button> | |
<button @click="forward">后退</button> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner', | |
methods:{ | |
back(){ | |
this.$router.back(); | |
}, | |
forward(){ | |
this.$router.forward(); | |
} | |
} | |
}; | |
</script> | |
<style> | |
</style> |
# src - 独享路由守卫
import Vue from 'vue' | |
import App from './App.vue' | |
// 引入 Vue 路由 | |
import VueRouter from 'vue-router' | |
// 引入路由器 | |
import router from './router/index' | |
Vue.config.productionTip = false | |
Vue.use(VueRouter) | |
new Vue({ | |
render: h => h(App), | |
router: router, | |
beforeCreate() { //beforeCreate: 第一个生命周期钩子,所有 Vue 以及数据代理还未开始执行之前 | |
Vue.prototype.$bus = this // 安装全局事件总线 | |
}, | |
}).$mount('#app') |
<template> | |
<div> | |
<div class="row"> | |
<Banner/> | |
</div> | |
<div class="row"> | |
<div class="col-xs-2 col-xs-offset-2"> | |
<div class="list-group"> | |
<!-- Vue 中借助 router-link 标签实现路由的切换 --> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/about" | |
>About | |
</router-link | |
> | |
<router-link :replace="true" class="list-group-item" active-class="active" to="/home" | |
>Home | |
</router-link | |
> | |
</div> | |
</div> | |
<div class="col-xs-6"> | |
<div class="panel"> | |
<div class="panel-body"> | |
<!-- 指定路由器组件的呈现位置 --> | |
<router-view></router-view> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Banner from "./components/Banner.vue"; | |
export default { | |
name: "App", | |
components: { | |
Banner, | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
新建 router 文件
// 该文件专门用于创建整个应用的路由器 | |
import VueRouter from 'vue-router' | |
// 引入组件啊 | |
import Aboout from '../pages/About' | |
import Home from '../pages/Home' | |
import News from '../pages/News' | |
import Message from '../pages/Message' | |
import Detail from '../pages/Detail' | |
// 创建一个路由器 | |
const router = new VueRouter({ | |
// 一级路由配置 | |
routes: [ | |
{ | |
name: 'guanyu', | |
path: '/about', | |
component: Aboout, | |
meta:{title:'关于'}, | |
}, | |
{ | |
name: 'zhouye', | |
path: '/home', | |
component: Home, | |
meta:{title:'主页'}, | |
children: [ | |
// 耳机路由配置 | |
{ | |
name:'xinwen', | |
path: 'news', | |
component: News, | |
meta:{isAuth: true,title:'新闻'}, | |
beforeEnter: (to, from, next) => { | |
if (to.meta.isAuth) { | |
//localStorage.getItem ():获取当前 cooker 里面的 school | |
if (localStorage.getItem('school') === 'rain') { | |
next()// 放行 | |
}else{ | |
alert('cooke信息不对') | |
} | |
}else{ | |
next() | |
} | |
} | |
}, | |
{ | |
name: 'xiaoxi', | |
path: 'message', | |
component: Message, | |
meta:{isAuth: true,title:'消息'}, | |
// 三级路由配置 | |
children: [ | |
{ | |
name: 'xiangqing', | |
path: 'detail', | |
component: Detail, | |
meta:{isAuth: true,title:'详情'}, | |
// 第三种写法 常用 值为函数 | |
props($route) { | |
return { id: $route.query.id, title: $route.query.title } | |
} | |
/* props (query){// 解构赋值 | |
return {id:query.id,title:query.title} | |
} */ | |
/* props ({query:{id,title} }){// 连续解构赋值 | |
return {id,title} | |
} */ | |
} | |
] | |
} | |
] | |
}, | |
] | |
}); | |
// 适用于判断是否登陆 | |
// 全局前置路由守卫 —— 初始化的时候被调用、每次路由切换之前被调用 | |
/* router.beforeEach ((to, from, next) => { | |
console.log (to, from, next); | |
if (to.meta.isAuth) { | |
//localStorage.getItem ():获取当前 cooker 里面的 school | |
if (localStorage.getItem ('school') === 'rain') { | |
next ()// 放行 | |
} else { | |
alert ('cooke 信息不对 ') | |
} | |
} else { | |
next () | |
} | |
}) */ | |
// 全局后置路由守卫 —— 初始化的时候被调用、每次路由切换之后被调用 | |
router.afterEach((to, from)=>{ | |
console.log('后置路由守卫',to,from) | |
document.title = to.meta.title || '硅谷系统' | |
}) | |
export default router |
新建 pages 文件夹
<template> | |
<h2>我是About的内容</h2> | |
</template> | |
<script> | |
export default { | |
name:'About' | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li>消息编号:{ {id} }</li> | |
<li>消息标题:{ {title} }</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name:'Detail', | |
props:['id', 'title'], | |
mounted(){ | |
console.log('detail',this) | |
} | |
} | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<h2>Home组件内容</h2> | |
<div> | |
<ul class="nav nav-tabs"> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/news" | |
>News | |
</router-link | |
> | |
</li> | |
<li> | |
<router-link | |
class="list-group-item" | |
active-class="active" | |
to="/home/message" | |
>Message | |
</router-link | |
> | |
</li> | |
</ul> | |
<!-- 路由缓存标签 keep-alive include: 指定需要缓存的组件名字 或者写成数组 --> | |
<!-- <keep-alive include='News'> | |
<router-view></router-view> | |
</keep-alive> --> | |
<!-- ------------------------------------------------------------------------ --> | |
<!-- 多组件路由缓存 --> | |
<keep-alive :include="['News', 'Message']"> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Home", | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<div> | |
<ul> | |
<li v-for="me in message" :key="me.id"> | |
<router-link | |
:to="{ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}" | |
>{ { me.title } } | |
</router-link | |
> | |
<button @click='pushShow(me)'>push查看</button> | |
<button @click='replaceShow(me)'>replace查看</button> | |
</li> | |
</ul> | |
<hr/> | |
<router-view></router-view> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "Message", | |
beforeDestroy(){ | |
console.log('Message即将被销毁') | |
}, | |
data() { | |
return { | |
message: [ | |
{ id: "001", title: "你好啊1" }, | |
{ id: "002", title: "你好啊2" }, | |
{ id: "003", title: "你好啊3" }, | |
], | |
}; | |
}, | |
methods:{ | |
pushShow(me){ | |
this.$router.push({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
}, | |
replaceShow(me){ | |
this.$router.replace({ | |
path: '/home/message/detail', | |
query: { id: me.id, title: me.title }, | |
}) | |
} | |
}, | |
}; | |
</script> | |
<style> | |
</style> |
<template> | |
<ul> | |
<li :style="{ opacity }">欢迎学习Vue</li> | |
<li>news001<input type="text"/></li> | |
<li>news002<input type="text"/></li> | |
<li>news003<input type="text"/></li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
name: "News", | |
data() { | |
return { | |
opacity: 1, | |
}; | |
}, | |
beforeDestroy() { | |
console.log("News即将被销毁"); | |
}, | |
activated() { | |
console.log("News被激活了"); | |
this.timer = setInterval(() => { | |
console.log("@"); | |
this.opacity -= 0.01; | |
if (this.opacity <= 0) this.opacity = 1; | |
}, 16); | |
}, | |
deactivated() { | |
console.log("News失活了"); | |
clearInterval(this.timer); | |
}, | |
};</script> | |
<style> | |
</style> |
下面放在 components 文件夹内
<template> | |
<div class="col-xs-offset-2 col-xs-8"> | |
<div class="page-header"> | |
<h2>Vue Router Demo</h2> | |
<button @click="back">前进</button> | |
<button @click="forward">后退</button> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name:'Banner', | |
methods:{ | |
back(){ | |
this.$router.back(); | |
}, | |
forward(){ | |
this.$router.forward(); | |
} | |
} | |
}; | |
</script> | |
<style> | |
</style> |
下一篇