VUE脚手架练习

脚手架安装的问题:

1.安装node.js,配置环境变量,cmd输入node -v和npm -v可以看到版本号(如果显示不是命令,确认环境变量是否配置成功,记得配置环境变量之后重新打开cmd,再去验证)

2.在安装cnmp时,大堆错误提示,记得以管理员身份打开cmd,在安装东西

3.在创建脚手架时,cmd中在索要创建脚手架项目的文件夹目录下输入vue create myvue1,选择Vue2,下载完成之后,输入npm run serve,打开所给网址,看到vue2的网页

4.运行main.js文件,右键->open in Integrated Terminal(或快捷键ctrl+~),打开终端,

在终端中输入:npm run serve

 如果出现错误,提示没有脚本权限

解决方法:参考(npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink-优快云博客),我这个执行到第二步就可以解决了

ref属性:

实现效果:点击按钮,获取uu和组件的dom元素,在控制台输出,并对uu的内容进行修改

代码:

App.vue

<template>
  <div id="app">
    {{ msg }}<br>
     <button @click="showDom">点我输出下方元素的DOM元素</button><br>
     <!-- <span id="ye">uu</span> -->
     <span ref="ye">uu</span>
     <HelloYa ref="hello"/> 
     <!-- <HelloYa id="hello"/>  -->
  </div>
</template>

<script>
import HelloYa from './components/Hello.vue'
export default {
  name: 'App',
  data(){
    return {
      msg:'你好你好'
    }  
  },
  methods:{
    showDom(){
      //使用refs获得HTML的标签元素,
      //和document.getElementById实现同样的效果
      // console.log(document.getElementById('ye'));
      // document.getElementById('ye').innerHTML = 'yy'
      console.log(this.$refs.ye);
      this.$refs.ye.innerHTML = 'yy'
      //获取hello组件的实例对象
      console.log(this.$refs.hello);
      //获取hello组件的根元素(比如template中的div)
      // console.log(document.getElementById('hello'));
    }
  },
  components: {
    HelloYa
  }
}
</script>

Hello.vue:

<template>
  <div>
    我是Hello组件:
     {{ message }}
  </div>
</template>

<script>
export default {
    name:'HelloYa',
    data(){
        return {
            message:'yan'
        }
    }
}
</script>

props的使用:组件可以复用,但是组件中的数据是动态的,可以被设置

实现效果:

文件App.vue、Student.vue(Student.vue报错时,注意可能时组件的name声明是没有使用驼峰法(例:StudentName))

App.vue:

<template>
  <div>
    <!-- 组件复用,数据是动态的 -->
    <!-- <StudentName name="uu" sex="n" age="19"/> -->
    <!-- age前加:代表,:代表动态绑定,运行引号中的表达式, -->
    <StudentName name="uu" sex="n" :age="4"/>
    <hr>
    <StudentName name="yy" sex="n" :age="2"/>
  
  </div>
</template>

<script>
import StudentName from './components/Student'
export default {
  name: 'App',
  data(){
    return {
      msg:'你好你好'
    }  
  },
  methods:{
  },
  components: {
    StudentName
  }
}
</script>

Student.vue:

<template>
  <div>
    <h2>{{ message }}</h2>
    姓名:{{ name }}<br>
    <!-- 性别:{{ age+1 }}<br> -->
    年龄:{{ age }}<br>
    性别:{{ sex }}<br>
  </div>
</template>

<script>
export default {
    name:'StudentName',
    data(){
        return {
            message:'我是学生',
            // name:'yan',
            // age:19,
            // sex:'nv'
            myname:this.name
        }
    },
    //props属性值一般不让修改,可以改但会报错,
    // 并且props中的数据优先级高于data中的数据(也就是说同时给相同属性赋值时,data中的数据会被覆盖)
    //要想实现修改props中的属性值,可以在data中加《myname:this.name》,在模板中使用{{ myname }},methods中使用myname进行操作

    //简单声明接收
    // props:['name', 'sex', 'age'],

    //限制接收:在接收的同时对数据进行类型限制
    // props:{
    //   name:String,
    //   age:Number,
    //   sex:String
    // },

    //限制接收:在接收的同时对数据进行类型限制+默认值指定+必要性限制
    props:{
      //required和default一般不同时设置
      name:{
        type:String,
        required:true //name是必要的
      },
      age:{
        type:Number,
        //在App组件中:使用<StudentName name="uu" sex="n" :age="4"/>验证default功能
        default:1
        // required:false
      },
      sex:{
        type:String,
        default:'女'
      }
    }

}
</script>

mixin混合(混入)

实现效果:

局部配置混合:

全局配置混合:

代码:

Student.vue:

<template>
  <div>
    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>
    <button @click="showName">点我弹窗</button>
    {{ a }}
  </div>
</template>

<script>
import { hunhe1, hunhe2 } from '../mixin';
export default {
    name:'StudentName',
    data(){
        return {
            name:'yan',
            age:'12'
        }
    },
    mixins:[hunhe1, hunhe2],
}
</script>

School.vue:

<template>
    <div>
      <h2>{{ name }}</h2>
      <h2>{{ address }}</h2>
      <button @click="showName">点我弹窗</button>
      {{ b }}
    </div>
  </template>
  
  <script>
  import { hunhe1, hunhe2 } from '../mixin';
  export default {
      name:'SchoolName',
      data(){
          return {
            name:'家里蹲',
            address:'哈尔滨',
            //当混合中的设置与组件中冲突时,以组件中为主
            a:2,
          }
      },
      mixins:[hunhe1, hunhe2],
      //当混合与组件中的钩子函数冲突时,都执行,先执行混合中的
      mounted(){
      console.log('你好,student');
    }
  }
  </script>
  

 mixin.js:

//功能:把多个组件公用的配置提取成一个混入对象
//组件中可借助混合复用配置相同的数据、函数
export const hunhe1 = {
    methods: {
        showName() {
            alert(this.name)
        }
    },
    mounted() {
        console.log('你好,混合1');
    }
}
export const hunhe2 = {
    data() {
        return {
            a: 1,
            b: 3
        }
    }
}

App.vue

<template>
  <div>
    <StudentName/>
    <hr>
    <SchoolName/>
  </div>
</template>

<script>
import StudentName from './components/Student'
import SchoolName from './components/School'
export default {
  name: 'App',
  methods:{
  },
  components: {
    StudentName,
    SchoolName
  }
}
</script>

记事本案例:实现效果:

 代码:

App.vue:

<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader :addTodo="addTodo"/>
				<!-- App给Item传数据需先传给List,再由List传给Item -->
				<MyList :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo"/>
				<MyFooter :todos="todos" :clearAllTodo="clearAllTodo" :checkAllTodo="checkAllTodo"/>
			</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:[
					{id:'001',title:'学习',done:false},
					{id:'002',title:'睡觉',done:false},
					{id:'003',title:'纠结',done:false},
				]
			}
		},
		methods: {
			//添加任务,与MyHeader相关
			addTodo(todoObj){
				//这个也可以
				// this.todos=this.todos.concat(todoObj)
				this.todos.push(todoObj)
			},
			// 删除任务,与MyItem有关
			deleteTodo(id){
				//这样删出问题,id索引不到他的数组当中的对象位置
				// this.todos.splice(id,1)

				//filter通过遍历数组每个元素,过滤出满足函数的元素值组成的数组
				//id为所要删除的元素id
				this.todos = this.todos.filter( todo => todo.id !== id )
			},
			// 选中任务(将任务中的done值取反),与MyItem有关
			checkTodo(id){
				this.todos.forEach((todo)=>{
					if(todo.id==id){
						todo.done =! todo.done
					}
				})
			},
			//底部中的清除已完成任务按钮(将todos中的todo中的done值为真的元素删除),与MyFooter有关
			clearAllTodo(){
				//filter函数为数组中的每个元素调用函数一次,并构造一个由所有返回真值的元素值组成的新数组
				// this.todos = this.todos.filter((todo)=>todo.done !== true)
				this.todos = this.todos.filter((todo)=>{
					return !todo.done
				})
			},
			//全选or取消全选(将全选按钮的状态与每个任务(todo)的选中状态(true or false)相关联),与MyFooter有关
			checkAllTodo(done){
				this.todos.forEach((todo)=>{
					todo.done = 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>

 MyHeader:

<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:['addTodo'],
		data() {
			return {
				title:''
			}
		},
		methods:{
			add(){
				const todoObj={id:nanoid(),title:this.title,done:false}
				//要在APP组件中修改todos
				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>

MyList:

<template>
	<ul class="todo-main">
		<MyItem :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo"/>
	</ul>
</template>

<script>
	import MyItem from './MyItem'
	export default {
		name:'MyList',
		props:['todos','deleteTodo','checkTodo'],
		components:{MyItem},
	}
</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>

 MyItem:

<template>
	<div>
		<li v-for="todo in todos" :key="todo.id">
		<label>
			<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
			<span>{{ todo.title }}</span>
		</label>
		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
	</li>
	</div>
</template>

<script>
	export default {
		name:'MyItem',
		props:['todos','deleteTodo','checkTodo'],
		methods: {
			//每个任务后面的删除事件
			handleDelete(id){
				this.deleteTodo(id)
			},
			//每个任务前面的复选框的点击事件
			// //(修改todo中的done值,然后复选框的状态在上面设置为:checked="todo.done"
			//就可以修改复选框的选中状态)
			handleCheck(id){
				this.checkTodo(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>

MyFooter.vue

<template>
	<!-- 当没有任务时,底下的footer模块不show -->
	<div class="todo-footer" v-show="todos.length">
		<label>
			<!-- 全选框 -->
			<!-- 方法1 -->
			<input type="checkbox" :checked="isAll" @change="checkAll"/>
			<!-- 方法2 -->
			<!-- <input type="checkbox" v-model="isAll"/> -->
		</label> 
		<span>
			<span>已完成{{ doneTotal }}</span> / 全部{{ todos.length }}
		</span>
		<!-- 清除已完成任务 -->
		<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
	</div>
</template>

<script>
	export default {
		name:'MyFooter',
		props:['todos','clearAllTodo','checkAllTodo'],
		computed:{
			//已完成任务的数量
			doneTotal(){
				//计算todos中的每个todo中的done值为true的数量
				return this.todos.reduce((pre,todo) => pre + (todo.done?1:0),0);
			},
			// 已完成前面的对勾(全选对勾)
			//方法1,使用checked和@change搭配使用
			isAll(){
				return this.doneTotal === this.todos.length && this.todos.length > 0
			}
			//方法2 与v-model搭配使用
			// isAll:{
			// 	//全选框是否勾选
			// 	get(){
			// 		return this.doneTotal === this.todos.length && this.todos.length > 0
			// 	},
			// 	//isAll被修改时set被调用
			// 	set(value){
			// 		this.checkAllTodo(value)
			// 	}
			// },
		},
		methods:{
			//清除所有完成的任务
			clearAll(){
				this.clearAllTodo()
			},
			//点击全选框时调用函数,拿到此时复选框按钮的状态(e.target.checked)
			checkAll(e){
				this.checkAllTodo(e.target.checked)
			} 
		}
}
</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>

 组件自定义事件的绑定、解绑,实现子给父传数据

代码:

App.vue

<template>
    <div>
        {{ message }}
     <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
     <School :getSchoolName="getSchoolName"/>
     <hr>
     <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
     <Student v-on:ai="getStudentName" @ai2="getStudentSex"/>
      <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
     <!-- <Student ref="student"/> -->
     <hr>
     <!-- 组件中不能直接绑定原生DOM事件,组件会把它当作自定义事件执行,需要在Student组件中绑定一下($emit) -->
     <!-- <Student @click="show"/> -->
      <!-- 可以通过加上.native给组件添加原生事件,把click给组件的根元素中执行 -->
     <!-- <Student @click.native="show"/> -->
    </div>
</template>

<script>
import Student from './components/Student.vue';
import School from './components/School.vue'
export default {
    name:'App',
    components:{
        Student,
        School
    },
    data(){
        return {
            message:'欢迎'
        }
    },
    methods:{
        show(){
            alert('你好')
        },
        getSchoolName(e){
            console.log(e);
        },
        getStudentName(name){
            console.log(name);
        },
        getStudentSex(sex){
            console.log(sex);
        }
    },
    mounted(){
        //绑定自定义事件
        // this.$refs.student.$on('ai',this.getStudentName)
        //绑定自定义事件(一次性)
        // this.$refs.student.$once('ai',this.getStudentName)
    }   
}
</script>

Student.vue

<template>
  <div class="test">
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">点我传给App学生名</button>
    <button @click="unbind">点我解绑</button>
  </div>
</template>

<script>
export default {
    name:'Student',
    data() {
        return {
            name:'yan',
            sex:'女'
        }
    },
    methods:{
      sendStudentName(){
        //触发Student组件实例上的ai事件
        this.$emit('ai', this.name)
        this.$emit('ai2', this.sex)
        this.$emit('click')
      },
      unbind(){
        //解绑一个自定义事件
        // this.$off('ai')
        //解绑多个自定义事件
        this.$off(['ai','ai2'])
        //解绑所有的自定义事件
        // this.$off()
      }
    }
}
</script>
<style  scoped>
.test {
  background-color: orange;
}
</style>

School.vue

<template>
  <div class="test">
    <h2 >学校:{{ name }}</h2>
    <h2>学校地址:{{ address}}</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>
   .test {
      background-color: skyblue;
    }
</style>

 全局事件总线:

实现效果:两个子组件之间传数据(Student传给School)。

收数据的在mounted中绑定:this.$bus.$on('checkTodo',this.checkTodo),并且在beforeDestroy中解绑:this.$bus.$off('checkTodo')

发送数据的在methods中的(点击)事件的函数中:this.$bus.$emit('checkTodo',id)

关键语句:min.js中的Vue.prototype.$bus = this//安装全局事件总线

Student中的this.$bus.$emit('hello',this.name)

School中的this.$bus.$on('hello',(data)=> { this.studentName = data})

代码:

main.js

//引入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),
    beforeCreate() {
        Vue.prototype.$bus = this//安装全局事件总线
    }
})

 App.vue

<template>
    <div>
        {{ message }}
     <Student/>
     <hr>
     <School/>
    </div>
</template>

<script>
//引入Student组件
import Student from './components/Student.vue';
import School from './components/School.vue'
export default {
    name:'App',
    components:{
        Student,
        School
    },
    data(){
        return {
            message:'欢迎'
        }
    },   
}
</script>

 Student.vue

<template>
  <div class="test">
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">学生名给School组件</button>
  </div>
</template>

<script>
export default {
    name:'Student',
    data() {
        return {
            name:'yan',
            sex:'女'
        }
    },
    methods:{
      sendStudentName(){
        this.$bus.$emit('hello',this.name)
      }
    }
}
</script>
<style  scoped>
.test {
  background-color: orange;
}
</style>

School.vue

<template>
  <div class="test">
    <h2 >学校:{{ name }}</h2>
    <h2>学校地址:{{ address}}</h2>
    <h3>我收到了Student传来的学生名称:{{ studentName }}</h3>
  </div>
</template>

<script>
export default {
    name:'School',
    data() {
        return {
            name:'家里蹲',
            address:'安阳',
            studentName:''
        }
    },
    mounted(){
      this.$bus.$on('hello',(data)=> {
        console.log('我是School组件,收到了Student传来的学生名字', data);
        this.studentName = data
      })
    },
    beforeDestroy() {
      //注意,括号空的话,会把所有绑定的事件解绑
      this.$bus.$off('hello')
    }
}
</script>
<style>
   .test {
      background-color: skyblue;
    }
</style>

消息的订阅与发布:可以实现任意两个组件之间的数据接收

 pubsub库的安装(消息与订阅):终端输入:npm i pubsub-js(消息与订阅相关的库很多,可以从github中下载)

使用:接收数据的组件,在mounted中订阅消息,在beforeDestroy取消订阅(注意,在接收的函数中,默认第一个接收的参数为消息名称,第二个才是传过来的数据id,需要在这个函数的参数中使用_占位符接收一下)

实现效果:将Student组件中的学生name传给school组件,并展示出来:

代码:main.js、App.vue、Student.vue、School.vue

main.js

//引入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)
})

 App.vue

<template>
    <div>
        {{ message }}
     <Student/>
     <hr>
     <School/>
     <hr>
    
    </div>
  
</template>

<script>
import Student from './components/Student.vue';
import School from './components/School.vue'
export default {
    name:'App',
    components:{
        Student,
        School
    },
    data(){
        return {
            message:'欢迎'
        }
    },   
}
</script>

 Student.vue

<template>
  <div class="test">
    <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:'yan',
            sex:'女'
        }
    },
    methods:{
      // student组件发布数据
      // 将student的name传给school
      sendStudentName(){
        pubsub.publish('msgName',this.name)
      }
    },
}
</script>
<style  scoped>
.test {
  background-color: orange;
}
</style>

 School.vue

<template>
  <div class="test">
    <h2 >学校:{{ name }}</h2>
    <h2>学校地址:{{ address}}</h2>
    <h3>{{ sname }}</h3>
  </div>
</template>

<script>
import pubsub from 'pubsub-js';
export default {
    name:'School',
    data() {
        return {
            name:'家里蹲',
            address:'安阳',
            sname:''
        }
    },
    methods:{
      //第一个传入的参数默认是消息名,如果不需要,也要使用_占位符接一下
      getStudentName(msgName,studentName){
        console.log('School组件收到了Student组件传来的消息:',msgName);
        console.log('School组件收到了Student组件传来的学生名:',studentName);
        this.sname = studentName
      }
    },
    //需要数据的订阅消息
    mounted(){
      this.pubName = pubsub.subscribe('msgName',this.getStudentName)
      //或者直接在这写函数
      // this.pubName = pubsub.subscribe('msgName',(msgName,data)=>{
      //   console.log(msgName);
      //   console.log(data);
      // })
    },
    //取消订阅
    beforeDestroy(){
      pubsub.unsubscribe(this.pubName)
    }
}
</script>
<style>
   .test {
      background-color: skyblue;
    }
</style>

todolist案例中的消息订阅发布实现deleteTodo(每个任务后面的删除功能)

methods: {
			deleteTodo(_,id){
				this.todos = this.todos.filter( todo => todo.id !== id )
			},
		},
mounted(){
			this.$bus.$on('checkTodo',this.checkTodo)
			// this.$bus.$on('deleteTodo',this.deleteTodo)
			//App接收数据,使用pubsub.subscribe订阅消息
			this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
		},
beforeDestroy(){
			// this.$bus.$off(['checkTodo','deleteTodo'])
			this.$bus.$off('checkTodo')
			pubsub.unsubscribe(this.pubId)
		}

发布数据的组件在methods函数中发布消息

<template>
	<div>
		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
	</div>
</template>

<script>
import pubsub from 'pubsub-js';
	export default {
		name:'MyItem',
		props:['todo'],
		methods: {
			handleDelete(id){
				//deleteTodo是函数名
				// this.deleteTodo(id)
				//deleteTodo是事件名
				// this.$bus.$emit('deleteTodo',id)
				//deleteTodo是消息名
				pubsub.publish('deleteTodo',id)
			},
	}
</script>

注意,开发者工具可以看到事件总线,但是看不到消息与订阅,因为是第三方库,vue的开发者工具不支持。另外,pubsub只能传两个参数,一个是消息名,一个是参数(传递的数据),要想传两个数据的话,不能用pubsub(以我目前的知识储备),通过实验,它接收不到第二个数据!!!

todoList加编辑功能:点击编辑按钮,span变成输入框(v-show="todo.isEdit")且自动获取焦点($nextTick和focus()),输入数据之后文本框失去焦点后,将todo中的值改为文本框输入的值,变回span,

实现效果:

关键语句:

给todo一个新的属性isEdit,并给其赋True值:this.$set(todo,'isEdit','true')

获得输入框中的值,当不能使用v-model时,可以使用e.targrt.value得到:

<template>

         <input type="text" v-model="todo.title" v-show="todo.isEdit"  @blur="handleBlur(todo.id,$event)" ref="inputTitle">

</template>

methods:{

           handleBlur(id,e){

                if(!e.target.value.trim()) return alert('不能为空')

                //console.log(e.target.value);

                this.$bus.$emit('updateTodo',id,e.target.value)

            }

}

通过ref获得input,通过this.$refs.inputTitle.focus(),让文本框获取焦点,但如果没有成功,考虑是否在文本框出现前就执行这句才导致失败的,修改为:让函数在Dom更新时调用(dom会在元素修改时更新) 函数,更新模板时调用函数让输入框获取焦点

this.$nextTick(function(){

                    this.$refs.inputTitle.focus()

                })

 主要实现的组件App.vue和MyItem.vue

App.vue

<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<!-- <MyHeader :addTodo="addTodo"/> -->
				<MyHeader @addTodo="addTodo"/>
				<!-- App给Item传数据需先传给List,再由List传给Item -->
				<MyList :todos="todos"/>
				<MyFooter :todos="todos" @clearAllTodo="clearAllTodo" @checkAllTodo="checkAllTodo"/>
			</div>
		</div>
	</div>
</template>

<script>
import pubsub from 'pubsub-js'
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'
import { event } from 'jquery';
	
	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter},
		data() {
			return {
				todos:JSON.parse(localStorage.getItem('todos')) || []
				// todos:[
					// {id:'001',title:'学习',done:false},
					// {id:'002',title:'睡觉',done:false},
					// {id:'003',title:'纠结',done:false},
				// ]
			}
		},
		methods: {
			//添加任务,与MyHeader相关
			addTodo(todoObj){
				//这个也可以
				// this.todos=this.todos.concat(todoObj)
				this.todos.push(todoObj)
			},
			// 删除任务,与MyItem有关
			deleteTodo(_,id){
				//这样删出问题,id索引不到他的数组当中的对象位置
				// this.todos.splice(id,1)
				//filter通过遍历数组每个元素,过滤出满足函数的元素值组成的数组
				//id为所要删除的元素id
				this.todos = this.todos.filter( todo => todo.id !== id )
			},
			// 选中任务(将任务中的done值取反),与MyItem有关
			checkTodo(id){
				this.todos.forEach((todo)=>{
					if(todo.id==id){
						todo.done =! todo.done
					}
				})
			},
			//底部中的清除已完成任务按钮(将todos中的todo中的done值为真的元素删除),与MyFooter有关
			clearAllTodo(){
				//filter函数为数组中的每个元素调用函数一次,并构造一个由所有返回真值的元素值组成的新数组
				// this.todos = this.todos.filter((todo)=>todo.done !== true)
				this.todos = this.todos.filter((todo)=>{
					return !todo.done
				})
			},
			//全选or取消全选(将全选按钮的状态与每个任务(todo)的选中状态(true or false)相关联),与MyFooter有关
			checkAllTodo(done){
				this.todos.forEach((todo)=>{
					todo.done = done
				})
			},
			updateTodo(id,e){
				this.todos.forEach((todo)=>{
					if(todo.id == id){
						todo.title = e
						todo.isEdit =! todo.isEdit
					}
				})
			}
		},
		mounted(){
			this.$bus.$on('checkTodo',this.checkTodo)
			//this.$bus.$on('deleteTodo',this.deleteTodo)
			//App接收数据,使用pubsub.subscribe订阅消息
			//注意这样写,调用deleteTodo时,第一个传的参数为订阅的消息名称,
			//需要在这个函数中的参数位置,使用_占位符接一下,否则,id传不过来
			this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)

			//update函数需要接两个参数过来,id和事件的输入框的值,这里使用pubsub的话只能传过来消息名和一个数据值
			//传两个数据目前不知道方法
			//所以总的来说,还是事件总线更方便
			this.$bus.$on('updateTodo',this.updateTodo)
		},
		beforeDestroy(){
			this.$bus.$off('checkTodo')
			pubsub.unsubscribe(this.pubId)
			this.$bus.$off('updateTodo')
		},
		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-edit {
		color: #fff;
		background-color: #3735b3;
		border: 1px solid hsla(226, 89%, 47%, 0.788);
	}

	.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>

 MyItem.vue

<template>
	<div>
		<li>
		<label>
			<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
			<span v-show="!todo.isEdit">{{ todo.title }}</span>
			<!-- 通过传递事件,e.target.value拿到文本框中输入的数据 -->
			<input 
			type="text" 
			v-model="todo.title" 
			v-show="todo.isEdit" 
			@blur="handleBlur(todo.id,$event)"
			ref="inputTitle">
		</label>
		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
		<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>
	</li>
	</div>
</template>

<script>
import pubsub from 'pubsub-js';
	export default {
		name:'MyItem',
		props:['todo'],
		methods: {
			//每个任务后面的删除事件
			handleDelete(id){
				//deleteTodo是函数名
				// this.deleteTodo(id)
				//deleteTodo是事件名
				// this.$bus.$emit('deleteTodo',id)
				//deleteTodo是消息名
				pubsub.publish('deleteTodo',id)
			},
			//每个任务前面的复选框的点击事件
			// //(修改todo中的done值,然后复选框的状态在上面设置为:checked="todo.done"
			//就可以修改复选框的选中状态)
			handleCheck(id){
				// this.checkTodo(id)
				this.$bus.$emit('checkTodo',id)
			},
			handleEdit(todo){
				//当前任务第一次被点击编辑按钮时,才会赋给todo一个isEdit的属性,否则直接赋值
				if(todo.hasOwnProperty('isEdit')){
					todo.isEdit = true
				}else{
					this.$set(todo,'isEdit','true')
				}
				//实现点击编辑按钮后,让输入框获取自动焦点的功能
				//这样不行,vue会先执行完hanleEdit之后,再解析模板,此时还没有input框(v-show的isEdit没有)
				// this.$refs.inputTitle.focus()
				//$nextTick在更新dom操作时调用函数
				this.$nextTick(function(){
					this.$refs.inputTitle.focus()
				})
			},
			handleBlur(id,e){
				if(!e.target.value.trim()) return alert('不能为空')
				this.$bus.$emit('updateTodo',id,e.target.value)
			console.log(e.target.value);
			}
		},
	}
</script>

<style scoped>
	/*item*/
	li {
		list-style: none;
		height: 36px;
		line-height: 36px;
		padding: 0 20px;
		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>

默认插槽:

实现效果:

代码:

App.vue:

<template>
	<div class="container">
		<Category title="美食">
			<img src="../images/flower.jpg" alt="">
		</Category>
		<Category title="游戏">
			<li v-for="(item, index) in games" :key="index">
				{{ item }}
			</li>
		</Category>
		<Category title="电影">
			<video controls src="../images/school.mp4"></video>			
		</Category>
	</div>
</template>

<script>
import Category from './components/Category.vue';

export default {
	name:'App',
	components:{Category},
	data(){
		return {
			foods:['鱼','虾'],
			games:['你好','你好好'],
			films:['猪猪侠','武林外传']
		}
	}
}
</script>

<style>
/* img、video放这里或者Category都行 */
img {
    width: 100%;
}
video {
    width: 100%;
}
</style>

Category.vue

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!-- 插槽 -->
    <slot>我是默认插槽,当没有内容时,我就会出现</slot>
  </div>
</template>

<script>
export default {
    props:['title','listData']

}
</script>

<style>
.category {
    background-color: skyblue;
    width: 200px;
    height: 300px;
    float: left;
    margin-left: 20px;
}
.category h3 {
    text-align: center;
    background-color: orange;
}
.category li {
    padding-left: 30px;
}
</style>

创建服务器,

新建一个文件夹(demo),使用VScode打开文件夹,打开终端(ctrl+~),输入npm int ,生成package.json,在终端中输入服务器的名字(test_server),一路回车,输入npm i express,生成package-lock.json文件。之后创建server.js文件,输入内容如下,

//server.js文件

const express = require('express')

const app = express()
app.use(express.static(__dirname + '/static'))

app.get('/person', (req, res) => {
    res.send({
        name: 'yanyan',
        age: 18
    })
})

app.listen(5005, (err) => {
    if (!err) console.log('服务器启动成功了');
})

,在终端中输入:node server,启动服务器,浏览器输入"http://localhost:5005/person"可以看到服务器的数据, 

再创建static文件夹,将前端写的静态资源放入static文件夹中,加入index.html文件,输入“你好”,再浏览器中输入 “http://localhost:5005/”,看到页面

如果是自己写好的文件夹,再Vue脚手架中写的页面,(有public\src等文件夹),在终端中输入:npm run build,得到dist文件,将此文件夹中的东西放入static文件夹中(将原来的index.html文件删掉),就可以了,ctrl+c停以下终端,再node server重启一下,在浏览器上输入Locahost:5005看到所写页面。

路由组件写的页面在 Vuerouter中的mode修改,hash就是路径中带有#,这个在服务器部署后的浏览器中可以刷新,history不可以刷新,(刷新出现404)

history和hash的区别

要想用history刷新不会出现404,得在server.js中修改:(参考链接https://www.npmjs.com/package/connect-history-api-fallback)

在终端中输入: npm i connect-history-api-fallback,

在server.js中添加:

const express = require('express')
const history = require('connect-history-api-fallback');//新添加


const app = express()
app.use(history()) //新添加
app.use(express.static(__dirname + '/static'))

app.get('/person', (req, res) => {
    res.send({
        name: 'yanyan',
        age: 18
    })
})

app.listen(5005, (err) => {
    if (!err) console.log('服务器启动成功了');
})

之后在终端中输入node server就可以实现页面更新了

elementUI的使用:

在main.js中引入elementUI,在APP.vue中使用,代码如下:

使用方法见官方文档(Element - The world's most popular Vue UI framework

也可以按需引入,方法参加网络

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'



//完整引入
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI)

//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el: '#app',
	render: h => h(App),

})

App.vue

<template>
	<div>
		<button>我是按钮</button>
		<hr>
		<el-button type="primary">主要按钮</el-button>
		<el-button type="success">成功按钮</el-button>
		<el-button type="info">信息按钮</el-button>
		<el-button type="warning">警告按钮</el-button>
		<hr>
		<div class="block">
			<span class="demonstration">默认</span>
			<el-date-picker
			v-model="value1"
			type="date"
			placeholder="选择日期">
			</el-date-picker>
		</div>	
   </div>
</template>

<script>
export default {
	name:'App',
    data() {
      return {
        pickerOptions: {
          disabledDate(time) {
            return time.getTime() > Date.now();
          },
        },
        value1: '',
      };
    }
  };
</script>

<style>

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值