你可以通過右下角的 按鈕切換為簡體顯示


scoped样式

1. 作用:让样式在局部生效,防止冲突。
2. 写法:<style scoped />

  • Student.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="demo">
<h2 class="title">学生姓名:{{name}}</h2>
<h2 class="atguigu">学生性别:{{sex}}</h2>
</div>
</template>

<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
}
}
</script>

<style lang="less" scoped>
.demo{
background-color: pink;
.atguigu{
font-size: 40px;
}
}
</>

  • School.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="demo">
<h2 class="title">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>

<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷atguigu',
address:'北京',
}
}
}
</script>

<style scoped>
.demo{
background-color: skyblue;
}
</style>
  • App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h1 class="title">你好啊</h1>
<School/>
<Student/>
</div>
</template>

<script>
import Student from './components/Student'
import School from './components/School'

export default {
name:'App',
components:{School,Student}
}
</script>

<style scoped>
.title{
color: red;
}
</style>

  • main.js
1
2
3
4
5
6
7
8
9
10
11
12
//引入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)
})

scoped样式

TodoList案例

1. 组件化编码流程:
​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
​ 1).一个组件在用:放在组件自身即可。
​ 2). 一些组件在用:放在他们共同的父组件上(<span style="color:red">状态提升</span>)。
​ (3).实现交互:从绑定事件开始。
2. props适用于:
​ (1).父组件 ==> 子组件 通信
​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数
3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的
4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

  • 静态html、css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>

<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
<ul class="todo-main">
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
<li>
<label>
<input type="checkbox"/>
<span>yyyy</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>

</body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*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;
}

/*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);
}

/*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;
}
/*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;
}

/*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;
}

  • 拆分成静态组件并完成逻辑

  • MyFooter.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
<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(){
//此处使用reduce方法做条件统计
/* const x = this.todos.reduce((pre,current)=>{
console.log('@',pre,current)
return pre + (current.done ? 1 : 0)
},0) */
//简写
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
},
//控制全选框
isAll:{
//全选框是否勾选
get(){
return this.doneTotal === this.total && this.total > 0
},
//isAll被修改时set被调用
set(value){
this.checkAllTodo(value)
}
}
},
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>

  • MyHeader.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<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',
//接收从App传递过来的addTodo
props:['addTodo'],
data() {
return {
//收集用户输入的title
title:''
}
},
methods: {
add(){
//校验数据
if(!this.title.trim()) return alert('输入不能为空')
//将用户的输入包装成一个todo对象
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件去添加一个todo对象
this.addTodo(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>

  • MyItem.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
<!-- <input type="checkbox" v-model="todo.done"/> -->
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>

<script>
export default {
name:'MyItem',
//声明接收todo、checkTodo、deleteTodo
props:['todo','checkTodo','deleteTodo'],
methods: {
//勾选or取消勾选
handleCheck(id){
//通知App组件将对应的todo对象的done值取反
this.checkTodo(id)
},
//删除
handleDelete(id){
if(confirm('确定删除吗?')){
//通知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: #ddd;
}

li:hover button{
display: block;
}
</style>
  • MyList.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<ul class="todo-main">
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
</ul>
</template>

<script>
import MyItem from './MyItem'

export default {
name:'MyList',
components:{MyItem},
//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
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>

  • App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"/>
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
</div>
</div>
</div>
</template>

<script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter.vue'

export default {
name:'App',
components:{MyHeader,MyList,MyFooter},
data() {
return {
//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
todos:[
{id:'001',title:'抽烟',done:true},
{id:'002',title:'喝酒',done:false},
{id:'003',title:'开车',done:true}
]
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter( todo => todo.id !== id )
},
//全选or取消全选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
//清除所有已经完成的todo
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
}
}
</script>

<style>
/*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>


  • main.js
1
2
3
4
5
6
7
8
9
10
11
12
//引入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)
})

TodoList案例

浏览器本地存储

1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3. 相关API:

  1. xxxxxStorage.setItem('key', 'value');
    该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
    
  2. xxxxxStorage.getItem('person');
    ​ 该方法接受一个键名作为参数,返回键名对应的值。
  3. xxx xxStorage.removeItem('key');
    ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。
  4. xxxxxStorage.clear()
    ​ 该方法会清空存储中的所有数据。
  5. 备注:
    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。
  • localStorage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>

<script type="text/javascript" >
let p = {name:'张三',age:18}

function saveData(){
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))

const result = localStorage.getItem('person')
console.log(JSON.parse(result))

// console.log(localStorage.getItem('msg3'))
}
function deleteData(){
localStorage.removeItem('msg2')
}
function deleteAllData(){
localStorage.clear()
}
</script>
</body>
</html>

localStorage

  • sessionStorage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>

<script type="text/javascript" >
let p = {name:'张三',age:18}

function saveData(){
sessionStorage.setItem('msg','hello!!!')
sessionStorage.setItem('msg2',666)
sessionStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))

const result = sessionStorage.getItem('person')
console.log(JSON.parse(result))

// console.log(sessionStorage.getItem('msg3'))
}
function deleteData(){
sessionStorage.removeItem('msg2')
}
function deleteAllData(){
sessionStorage.clear()
}
</script>
</body>
</html>

sessionStorage

TodoList案例本地存储

  • App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"/>
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
</div>
</div>
</div>
</template>

<script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter.vue'

export default {
name:'App',
components:{MyHeader,MyList,MyFooter},
data() {
return {
//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
todos:JSON.parse(localStorage.getItem('todos')) || []
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter( todo => todo.id !== id )
},
//全选or取消全选
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>
/*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>