一、三个简单操作
1.绑定文本
使用{{}}绑定文本
<div id="app">
<!-- {{}}是表达式的意思 -->
<h1>{{ message+"Vue" }}</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建vue的实例
new Vue({
//将div与Vue建立联系
el:"#app",
//选项里定义数据message的值为hello world
data:{
message:"hello world"
}
})
</script>
结果为:hello worldVue
2.绑定属性
指令:
v-blind:属性
简写::属性
在页面插入图片:
<body>
<div id="app">
<!-- 下面为简写,全部应该是这样:<img v-bind:src="pic" alt=""> -->
<img :src="pic" alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建vue的实例
new Vue({
//将div与Vue建立联系
el:"#app",
pic:"images/img1.png"
}
})
</script>
3.绑定事件
事件修饰符:v-on
语法:v-on:事件类型=”方法”
简写:@事件类型=”方法”
点击按钮,弹出框
<div id="app">
<button v-on:click="sayHi">按钮</button>
<button @click="sayHi">测试按钮</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建vue的实例
new Vue({
methods:{
sayHi(){
alert("hi")
}
}
})
</script>
eg1.点击按钮,切换图片
<div id="app"> <!-- 下面为简写,全部应该是这样:<img v-bind:src="pic" alt=""> --> <img :src="pic" alt=""> <button @click="changeImg">改变图片</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> //创建vue的实例 new Vue({ //将div与Vue建立联系 el:"#app", methods:{ changeImg(){ this.pic="images/img3.jpg"; } } }) </script>
eg2.点击加号数字加一,点击减号,数字减一
<body> <div id="app"> <button @click="sub">-</button> <span>{{number}}</span> <button @click="add">+</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> new Vue({ el:"#app", data:{ number:0 }, methods:{ add(){ this.number++ }, sub(){ if(this.number>0) this.number-- } } }) </script> </body>
二、使用vue/cli工具创建一个vue项目(需要Node环境)
1.步骤
全局安装vue/cli工具:cnpm install -g @vue/cli
使用vue/cli常见vue项目:vue create hello
启动服务器:npm run serve
2.组文件开发概述
以vue为后缀的文件是vue的单文件组件,可以理解为一个可以自定义的、有更强大功能的标签
用这样的组件拆分的方式开发项目,思路清晰,简介高效,而且还可以复用相同的组件
3.使用vue实践
在vscode里打开vue文件,打开终端,进入node_modules文件所在的目录,输入npm run serve
vue文件:
<template>
<!--网页模版,编写html文件-->
<!--该页面只能写一个标签,若要写多个标签,可以把标签写在另一个标签里面,如将h1标签写在div标签里-->
<div>
<h1 :title="message">{{message}}</h1>
<button @click="showHi">showHi</button>
</div>
</template>
<script>
// js代码
export default {
data(){
return {
message:"hello vue"
}
},
methods:{
showHi(){
alert("Hi~")
}
}
}
</script>
<style>
/* css代码 */
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
4.模版语法
(1)指令
是带有v-前缀的特殊属性
1)绑定属性
v-bind
2)绑定事件
v-on
3)控制元素的可见性
v-if和v-show都可以控制元素的可见性,值为false不可见,为true可见
区别:v-if不渲染dom,而v-show渲染dom,再将元素设置成display:none
eg1.将两个元素隐藏:
<h1 v-if="false" :title="message">{{message}}</h1>
<button v-show="flase" @click="showHi">showHi</button>
在调试工具里显示如下:
eg2.设置效果,若登录,显示“欢迎”,未登录显示“请登录”,不显示欢迎
<template>
<div>
<p v-if="isLogin">欢迎</p>
<p v-if="!isLogin"><a href="">请登录</a></p>
</div>
</template>
<script>
export default {
data(){
return {
isLogin:false
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
4)显示列表
v-for
eg.制作页面显示如下效果
<template> <div> <p v-if="isLogin">欢迎</p> <p v-if="!isLogin"><a href="">请登录</a></p> <ul> <li v-for="(fruit,index) of fruits" :key="index"> <p>水果名称:{{fruit}}</p> <p>水果序号:{{index+1}}</p> </li> </ul> <table> <thead> <th>序号</th> <th>姓名</th> <th>年龄</th> </thead> <tbody> <tr v-for="(stu,i) of students" :key="i"> <td>{{i+1}}</td> <td>{{stu.name}}</td> <td>{{stu.age}}</td> </tr> </tbody> </table> </div> </template> <script> export default { data(){ return { isLogin:false, fruits:["香蕉","苹果","鸭梨"], students:[ {name:"小明",age:1}, {name:"花花",age:2}, {name:"好好",age:3}, ] } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } </style>
效果如图:
(2)组件嵌套
(1)组件命名:大写字母开头,驼峰命名
(2)注册组件:
1)引入组件:
先在script标签里引入组件:import 组件名 from “路径”
再在export default里注册新组件,
eg.文件如下,现将组件HelloWorld.vue和MenuList.vue引入APP.vue中并使用
APP.vue文件内代码如下:
<template>
<div>
<hello></hello>
<MenuList></MenuList>
</div>
</template>
<script>
import HelloWorld from"./components/HelloWorld.vue"
import MenuList from"./components/MenuList.vue"
export default {
components:{
hello:HelloWorld,
MenuList:MenuList
// 当把新建的组件与引入的组件命名为同名时,可以简写为(标签也同名):
// HelloWorld,MenuList
}
}
</script>
当组件作为标签在template里被使用时,在不与原来的组件重名的前提下,不区分大小写,同时还可以将两个单词用-分开,如
<menu-list></menu-list>一样可以使用
2)创建组件
在script标签里的export default里创建组件,然后在template里引用
eg.
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
data(){
return {
message:"hello vue!"
}
}
}
</script>
(3)组件传值
1)组件关系
①父级向子级传递数据:使用属性传递数据
在父级中:
先引入子级,再在父级中设置要传递的属性值,再在child组件里添加属性,并将属性值传递,
在子级中,使用props引入属性,然后在template中绑定属性,即可实现父级向子级传递数据
父级:
<template>
<div>
<h1>hello</h1>
<Child :msg="message"></Child>
</div>
</template>
<script>
import Child from "./components/Child"
export default {
components:{Child},
data(){
return {
message:"父级向子级传递数据"
}
}
};
</script>
子级:
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
props:["msg"]
}
</script>
②子级向父级传递数据:使用自定义事件传值
父级:
先将子级的组件绑定事件(@事件名=”事件的函数”),再在export default里编辑事件函数
子级:
在子级里创建了一个按钮,绑定点击事件,再在export default里编辑点击事件的函数,其中使用$emit触发父级中的myevent事件,并传递数据
父级:
<template>
<div>
<h1>hello!{{childData}}</h1>
<Child @myevent="changeData" :msg="message"></Child>
</div>
</template>
<script>
import Child from "./components/Child"
export default {
components:{Child},
data(){
return {
message:"父级向子级传递数据",
childData:""
}
},
methods:{
changeData(childData){
this.childData=childData;
}
}
};
</script>
子级:
<template>
<div>
<h1>{{ msg }}</h1>
<button @click="sendData">传递数据</button>
</div>
</template>
<script>
export default {
props:["msg"],
data(){
return{
childData:"子级向父级传递数据"
}
},
methods:{
sendData(){
// $emit可以触发事件
this.$emit("myevent",this.childData)
}
}
}
</script>
eg.实现计数器效果
Carts.vue
<template> <div> <h1>购物车</h1> <ul> <li v-for="(v,i) of fruits" :key="i"> {{ v.name }} 单价:{{ v.price }} 数量: <Counter :qu="v.qu" :index="i" @add="add" @sub="sub"> </Counter> </li> </ul> </div> </template> <script> import Counter from "./Counter.vue" export default { components:{Counter}, data(){ return { fruits:[ {name:"苹果",price:"3.14",qu:"0"}, {name:"鸭梨",price:"2.17",qu:"0"}, {name:"苹果",price:"2.17", qu:"0"}, ] } }, methods:{ add(index){ this.fruits[index].qu++ }, sub(index){ if(this.fruits[index].qu>0){ this.fruits[index].qu-- } } } }; </script>
Counter.vue
<template> <span> <button @click="sub">-</button> <span>{{ qu }}</span> <button @click="add">+</button> </span> </template> <script> export default{ props:["qu","index"], methods:{ sub(){ this.$emit("sub",this.index) }, add(){ this.$emit("add",this.index) } } } </script>
APP.vue
<template> <div> <Carts></Carts> </div> </template> <script> import Carts from "./components/Carts" export default { components:{Carts}, }; </script>
效果为:
③非父子级传递数据:定义一个共享数据的状态
创建一个store.js文件,在里面定义一个共享数据的状态,再在两个Brother.vue和Sister.vue组件里引入该文件,使两个文件通过js文件传递数据
先创建两个组件Brother.vue和Sister.vue,在两个组件里引入store.js文件,再在两个组件里使用store里的数据,然后,在Brother里创建按钮改变数据,点击按钮后,改变store里state的message,可以实现两个组件引用的message同步改变
Brother.vue
<template>
<div>
<h1>brother <button @click="changeData">改变数据</button></h1>
<p>{{ state.message }}</p>
</div>
</template>
<script>
import store from "../store"
export default{
data(){
return {
// 使用store.js里的数据
state:store.state
}
},
methods:{
changeData(){
store.setStateMessage("bother")
}
}
}
</script>
Sister.vue
<template>
<div>
<h1>sister</h1>
<p>{{ state.message}}</p>
</div>
</template>
<script>
import store from "../store"
export default{
data(){
return {
// 使用store.js里的数据
state:store.state
}
}
}
</script>
store.js
export default{
state:{
message:"hello vue"
},
// 通过方法的方式操作state
setStateMessage(str){
this.state.message=str;
}
}
APP.vue
<template>
<div>
<brother></brother>
<Sister></Sister>
</div>
</template>
<script>
import Brother from "./components/Brother"
import Sister from "./components/Sister"
export default {
components:{Brother,Sister}
};
</script>
总结:
将系统拆分成组件,一方面降低了功能的耦合,但是另一方面也提升了数据传输的难度,这里的利弊得失需要自己摸索
APP.vue
<template>
<div>
<Carts></Carts>
<!-- <brother></brother>
<Sister></Sister> -->
<!-- <control :totalPrice="totalPrice"></control> -->
</div>
</template>
<script>
import Carts from "./components/Carts"
// import Control from './components/Control.vue';
// import Control from "./components/Control.vue"
// import Brother from "./components/Brother"
// import Sister from "./components/Sister"
export default {
components:{Carts},
};
</script>
Counter.vue
<template>
<span>
<button @click="sub">-</button>
<span>{{ qu }}</span>
<button @click="add">+</button>
</span>
</template>
<script>
// import store from "../store"
export default{
props:["qu","index"],
methods:{
sub(){
this.$emit("sub",this.index)
},
add(){
this.$emit("add",this.index)
}
}
}
</script>
Carts.vue
<template>
<div>
<h1>购物车</h1>
<ul>
<li v-for="(v,i) of fruits" :key="i">
{{ v.name }}
单价:{{ v.price }}
数量:
<Counter
:qu="v.qu"
:index="i"
@add="add"
@sub="sub">
</Counter>
</li>
</ul>
<!-- <h1>总价为:</h1> -->
</div>
</template>
<script>
import Counter from "./Counter.vue"
export default {
components:{Counter},
data(){
return {
fruits:[
{name:"苹果",price:"3.14",qu:"0"},
{name:"鸭梨",price:"2.17",qu:"0"},
{name:"苹果",price:"2.17", qu:"0"},
]
}
},
methods:{
add(index){
this.fruits[index].qu++
},
sub(index){
if(this.fruits[index].qu>0){
this.fruits[index].qu--
}
}
}
};
</script>
三、计算属性与侦听器
computed:计算属性
watch:侦听器(监听器)
1.计算属性
data属性和computed属性定义的值都可以直接绑定在表达式中,如果某些值需要通过计算才能得到,那使用计算属性就再合适不过了
name=firstName+lastName
普通情况data与computed效果一样:
eg1.显示简单文字内容
<template>
<div id="app">
<h1>计算属性与侦听器</h1>
<p>{{title}}</p>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data(){
return{
title:"hello world"
}
},
// 下面的computed为对象,里面可以定义多个函数
computed:{
message(){
return "hello computed"
}
}
}
</script>
如果显示内容为简单表达式,可以直接写在页面
eg2.简单表达式
<template>
<div id="app">
<h1>计算属性与侦听器</h1>
<p>{{ firstName+lastName}}</p>
</div>
</template>
<script>
export default {
data(){
return{
firstName:"张",
lastName:"三"
}
}
</script>
但是若表达式有多个计算过程,则使用computed属性
eg3.计数器效果
<template>
<div id="app">
<h1>计算属性与侦听器</h1>
<p>单价:{{ price }}元</p>
<p>数量:
<button @click="sub">-</button>
{{ quatity }}
<button @click="add">+</button>
</p>
<p>折扣:{{ discount }}折</p>
<p>总价:{{ totalPrice }}元</p>
</div>
</template>
<script>
export default {
data(){
return{
price:"99",
quatity:1,
discount:0.5
}
},
computed:{
totalPrice(){
return this.price*this.quatity*this.discount
}
},
methods:{
sub(){
if(this.quatity>0)
this.quatity--
},
add(){
this.quatity++
}
}
}
</script>
2.侦听器
侦听一个属性的变化
侦听器也可实现computed的效果:
eg.侦听器实现计数器效果
<template>
<div id="app">
<h1>计算属性与侦听器</h1>
<p>单价:{{ price }}元</p>
<p>数量:
<button @click="sub">-</button>
{{ quatity }}
<button @click="add">+</button>
</p>
<p>折扣:{{ discount }}折</p>
<p>总价:{{ totalPrice }}元</p>
</div>
</template>
<script>
export default {
data(){
return{
price:"99",
quatity:0,
discount:0.5,
totalPrice:0
}
},
// 监听quatity,每次quatity变化就会调用quatity方法
watch:{
quatity(val){
this.totalPrice= this.price*val*this.discount;
}
},
methods:{
sub(){
if(this.quatity>0)
this.quatity--
},
add(){
this.quatity++
}
}
}
</script>
总结:一个值的改变会影响多个值(或处理多件事),使用侦听器。(为了观察一个值)
多个值的改变,为了得到一个结果,使用计算属性(为了得到一个值),如上面的计数器,只要totalPrice
实际开发中,大部分问题都可以使用computed解决
四、生命周期钩子
1.作用
页面加载的时候,主动执行某些程序
2.Vue实例
(1)new Vue()创建一个Vue实例,所有组件其实都是Vue实例
(2)Vue实例的生命周期钩子(函数)
每个Vue实例在被创建时(new Vue)都要经过一系列的初始化过程,在该过程默认会调用一些Vue内置定义好的函数,这些函数就是Vue的生命周期钩子
created()组件初始化完成
mounted()模版已创建
eg.加载组件自动执行生命周期钩子
APP.vue文件
<template>
<div id="app">
<h1>hello world</h1>
</div>
</template>
<script>
export default {
mounted(){
console.log("这是mounted函数的内容")
},
created(){
console.log("这是created函数的内容")
}
}
</script>
在该例中,并没有调用函数,但加载组件完成后会自动执行这两个函数
eg1.异步加载水果列表
<template>
<div id="app">
<h1>水果列表</h1>
<p v-if="loading">loading...</p>
<ul v-if="!loading">
<li v-for="(item,index) of fruitList" :key="index">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return{
fruitList:[],
loading:true
}
},
created(){
this.getData();
},
methods:{
getData(){
// 通过计时器模拟一个ajax获取数据的方法
setTimeout(()=>{
this.fruitList=["香蕉","苹果","鸭梨"],
this.loading=false
},2000)
}
}
}
</script>
使用计数器设置2s的加载过程,组件加载过程显示loading...,加载完成后不显示loading...
五、插槽、DOM、过滤器
1.插槽
(1)作用:可以让我们更灵活地使用组件,增强组件的扩展性;
让我们创建更灵活、易扩展组件:自定义button,自定义table等;
开发或使用UI库,了解组件制作原理
eg.通过下面的按钮组件,如何实现按钮文本的自定义,可以通过属性传值,也可以通过插槽实现
eg.通过父级向子级传递数据(用属性),将按钮的名字自定义设置
APP.vue
<template> <div id="app"> <MyButton text="提交"></MyButton> <MyButton text="注册"></MyButton> </div> </template> <script> import MyButton from "./components/MyButton.vue" export default { components:{MyButton} }; </script>
MyButton.vue
<template> <div> <button>{{ text }}</button> </div> </template> <script> export default{ props:["text"] } </script>
eg.通过插槽实现自定义按钮名称
App.vue
<template> <div id="app"> <MyButton >提交</MyButton> <MyButton >注册</MyButton> </div> </template> <script> import MyButton from "./components/MyButton.vue" export default { components:{MyButton} }; </script>
MyButton.vue
<template> <div> <button> <!-- 插槽 --> <slot></slot> </button> </div> </template>
自定义的内容会以插槽的方式插入到子组件的slot标签的位置
(2)具名插槽
可以在自定义的组件里写多个插槽,通过命名的方式区别不同的插槽显示不同的内容
使用template显示插入插槽的内容,使用v-slot区别不同插槽
eg.
Layout.vue
<template> <div> <div class="header"> <slot name="header"></slot> </div> <div class="conent"> <slot name="content"></slot> </div> <div class="footer"> <slot name="footer"></slot> </div> </div> </template>
APP.vue
<template> <div id="app"> <Layout> <!-- template中的内容为要插入到插槽的内容 --> <!-- 使用v-slot对应插槽的名字 --> <template v-slot:header> <h1>header</h1> </template> <template v-slot:content> <h1>content</h1> </template> <template v-slot:footer> <h1>footer</h1> </template> </Layout> </div> </template> <script> import Layout from "./components/Layout.vue" export default { components:{Layout} }; </script>
2.DOM操作
(1)之前学习回顾
DOM:文档对象模型
DOM节点:元素节点、属性节点、文本节点
通过操作DOM实现页面效果:jQuery
eg1.将h1标签 的内容修改成hello world
$(“h1”).text(“hello world”)
(2)虚拟DOM
Vue中的数据变化并不是直接改变DOM,而是通过改变虚拟DOM,将真实的DOM与虚拟的DOM作比较,并计算变更差异,进而修改DOM中有变化的内容
获取真实DOM:
1)获取元素样式
①可以直接通过js方法获取
先通过querySelector获取节点,再用window.getComputedStyle()获取节点的样式
<template>
<div id="app">
<div id="box">hello</div>
</div>
</template>
<script>
// import Layout from "./components/Layout.vue"
export default {
mounted(){
let box=document.querySelector("#box");
let style=window.getComputedStyle(box);
console.log(style.height)
}
};
</script>
<style>
div{
width:100px;
height: 100px;
background-color: red;
}
</style>
②通过vue获取dom(更方便)
在标签里指定属性ref,再使用vue实现获取真实dom
<template>
<div id="app">
<div ref="box">hello</div>
</div>
</template>
<script>
export default {
mounted(){
let box=this.$refs.box;
let style=window.getComputedStyle(box);
console.log(style.height)
}
};
</script>
<style>
div{
width:100px;
height: 100px;
background-color: red;
}
</style>
2)获取元素宽度
<template>
<div id="app">
<h1 ref="title">hello world</h1>
</div>
</template>
<script>
export default {
mounted(){
let h1=this.$refs.title;
let width=window.getComputedStyle(h1).width;
console.log(width)
}
};
</script>
<style>
div{
width:200px;
height: 100px;
background-color: red;
}
</style>
总结:
Vue应用开发的过程中,大部分情况是不需要获取真实DOM的,不要把jQuery的思想带到Vue中
3.过滤器
通过固定算法重新组织数据
将字符串拆分成字母:使用split,再将字母连接用join
APP.vue文件如下:
<template>
<div id="app">
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
data(){
return{
message:"hello"
}
}
};
</script>
在调试工具的控制台分别输入如下语句:
let str="hello";
str.split("").join();
结果为:'h,e,l,l,o'
接下来通过过滤器实现该功能
<template>
<div id="app">
<!-- |前面是原始数据,后面是过滤器 -->
<h1>{{ message | mySplit}}</h1>
<h1>{{ title | mySplit}}</h1>
</div>
</template>
<script>
export default {
// 过滤器
filters:{
// 定义多个过滤器
// 有形参,为过滤器输入的值
mySplit(value){
// spilt()将字符串拆分成数组,join()将数组连接成字符串
return value.split("").join();
}
},
data(){
return{
message:"hello",
title:"world"
}
}
};
</script>
eg1.日期格式化
将“2020-1-1”转化成“2020年1月1日”
<template>
<div id="app">
<h1>{{ date1 |dateFormate }}</h1>
<h1>{{ date2 |dateFormate }}</h1>
</div>
</template>
<script>
export default {
filters:{
dateFormate(value){
let date=new Date(value);
let year=date.getFullYear();
let month=date.getMonth()+1;
let d=date.getDate();
return `${year}年${month}月${d}日`
}
},
data(){
return{
date1:"2020-1-1",
date2:"2022-6-5"
}
}
};
</script>
六、表单
1.数据的双向绑定
通过v-model指令在文本输入框中创建双向数据绑定
eg1.输入文本,将文本内容传给h1
<template>
<div id="app">
<h1>{{message}}</h1>
<input type="text" v-model="message">
</div>
</template>
<script>
export default {
data(){
return{
message:""
}
}
};
</script>
eg2.简单提交表单
<template>
<div id="app">
<!-- 表单提交有一个刷新页面的默认行为,取消该行为:用prevent修饰符 -->
<form @submit.prevent="postData">
<!-- ajax实现表单提交 -->
<input type="text" v-model="message">
<button>提交表单</button>
</form>
</div>
</template>
<script>
export default {
data(){
return{
message:""
}
},
methods:{
postData(){
console.log(this.message)
}
}
};
</script>
2.常用表单控件
文本输入框、密码输入框、下拉菜单、单选框、复选框、提交按钮
eg实现提交表单
<template>
<div id="app">
<!-- 表单提交有一个刷新页面的默认行为,取消该行为:用prevent修饰符 -->
<form @submit.prevent="postData">
<!-- ajax实现表单提交 -->
<div>
<label for="">用户名:</label>
<input type="text" v-model="formData.username">
</div>
<div>
<label for="">密码:</label>
<input type="password" v-model="formData.password">
</div>
<div>
<label for="">爱好:</label>
<select v-model="formData.hobby">
<!-- 此处绑定是value值 -->
<option value="basketball">篮球</option>
<option value="football">足球</option>
<option value="羽毛球">羽毛球</option>
</select>
</div>
<div>
<label for="">性别:</label>
<label for="">男</label>
<!-- 男女两个v-model要绑定一个属性值才能实现单选 -->
<input type="radio" value="男" v-model="formData.sex">
<label for="">女</label>
<input type="radio" value="女" v-model="formData.sex">
</div>
<div>
<label for="">技能:</label>
<label for="">前端</label>
<input type="checkbox" value="前端" v-model="formData.skill">
<label for="">java</label>
<input type="checkbox" value="java" v-model="formData.skill">
</div>
<button>提交表单</button>
</form>
</div>
</template>
<script>
export default {
data(){
return{
// 将表单数据写在一个对象里
formData:{
username:"",
password:"",
hobby:"",
sex:"",
skill:[]
}
}
},
methods:{
postData(){
console.log(this.formData)
}
}
};
</script>
效果如下:
七、数据交互
1.知识回顾
http协议:前端(浏览器)发送请求,服务器给予响应
请求方法:get(查询)、post(添加)、put(修改)、delete(删除)
ajax:不刷新页面与后台交互数据
axios:封装好的ajax模块
koa:基于node的web框架
2.示例
初始化vue项目
①创建vue项目
②npm install --save axios
③import axios from “axios”
实现水果列表的添加和删除
APP.vue
<template>
<div>
<form @submit.prevent="postData">
<input type="text" v-model="fruit">
<button>添加</button>
</form>
<ul>
<li v-for="item,index of fruitList" :key="index">
{{item}}
<!-- 根据索引删除 -->
<button @click="del(index)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import axios from "axios";
export default {
data(){
return{
fruit:"",
fruitList:[]
}
},
methods:{
//获取数据
getFruitList(){
axios.get("http://127.0.0.1:3000/fruits").then(res=>{
this.fruitList=res.data;
})
} ,
//添加数据
postData(){
axios.post("http://127.0.0.1:3000/fruits",{
fruit:this.fruit
}).then(res=>{
//响应后重新获取数据,更新列表
this.getFruitList();
})
},
// 删除数据
del(index){
axios.delete(`http://127.0.0.1:3000/fruits${index}`)
.then(res => {
//响应后重新获取数据,更新列表
this.getFruitList();
})
}
},
created(){
this.getFruitList();//初始化数据
}
}
</script>
server.js
const Koa=require("koa");
const router=require("koa-router")();//设置路由
const parser=require("koa-parser");//获取post请求数据
// 安装cnpm install --save koa2-cors
const cors=require('koa2-cors');//允许跨域
const app=new Koa();
app.use(parser());
app.use(cors());
//模拟数据库
const fruitList=["香蕉","苹果","鸭梨"];
//get方法:获取水果列表
router.get("/fruits",async ctx => {
ctx.body=fruitList;
})
//post添加
//注意:使用post要添加koa-parser(在上面)
router.post("/fruits",async ctx => {
//获取数据
let fruit=ctx.request.body.fruit;
//push方法可以在array结尾追加数据
fruitList.push(fruit);
ctx.body=true;
})
//put修改s
router.put("/fruits/:index",async ctx => {
let index=ctx.params.index;
let fruit=ctx.request.body.fruit;
dataList.splice(index,1,fruit);
ctx.body=fruitList;
})
//delete删除
router.delete("/fruits/:index", async ctx => {
let index=ctx.params.index;
fruitList.splice(index,1);
ctx.body=true;
})
app.use(router.routes());
app.listen("3000",()=>{
console.log("server is running");
})
3.项目实践
在真实的项目中,我们会对axios进行进一步封装,以便于更轻松的发送请求
通过配置文件,设置项目开发与项目部署的无缝衔接
八、路由
之前讲解的所有功能都是在同一个页面完成的,真正的web应用一定是有多个页面的,这就需要路由
根据创建的Vue项目的结构可知,可以通过router-link标签,再用to实现跳转(自定义组件的页面),而router文件下的index.html文件中,引入了组件及路径
注意:router-link标签一定要使用to,否则不显示内容
一般页面中的局部内容的组件放在components里用来复用,整体页面的组件放在views里,基本不是复用,而是一个单独的页面
eg.制作博客系统,实现菜单跳转和登录、注销功能,未登录不可访问任何页面
文件如下:
APP.vue
<template>
<div id="app">
<nav>
<!-- router-link类似于a标签,可实现页面跳转,跳转属性是to -->
<!-- a标签跳转的属性是href -->
<router-link to="/">首页</router-link> |
<router-link to="/blog">博客</router-link>|
<router-link to="/video">视频</router-link>
<span v-if="showUser"> | |欢迎:{{ username }} <button @click="logout">注销</button></span>
</nav>
<!-- router-view是一个显示网页内容的位置 -->
<router-view/>
</div>
</template>
<script>
export default{
data(){
return {
username:localStorage.getItem("usr"),
showUser:localStorage.getItem("usr")
}
},
watch:{
//当路由路径改变就执行函数(即切换页面就执行
'$route.path':function(){
this.username=localStorage.getItem("usr"),
this.showUser=localStorage.getItem("usr")
}
},
methods:{
logout(){
//清空本地存储的值
localStorage.clear();
this.$router.push("/login")
}
}
}
</script>
index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import BlogView from "../views/BlogView.vue"
import VideoView from "../views/VideoView.vue"
import LoginView from "../views/LoginView.vue"
const routes = [
{
path: '/',
name: 'HomeView',
component: HomeView
},
{
path:"/blog",
name:"BlogView",
component:BlogView
},
{
path:"/video",
name:"VideoView",
component:VideoView
},
{
path:"/login",
name:"LoginView",
component:LoginView
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
//导航守卫:router有一个方法beforeEach函数,其参数为一个回调函数,
//回调函数有三个参数,分别是to,from,next
//to:访问到哪,from:从哪访问,next:继续访问
router.beforeEach((to,from,next)=>{
//如果to的路径不是登录页面的路径
if(to.path!=="/login"){
//如果可以拿到本地存储用户名(说明已经登录),则继续访问
if(localStorage.getItem("usr")){
next();
//如果没有本地存储用户名(即已经注销或还未登录),则返回登录页
}else{
//重定向
next("/login")
}
}
//如果to的路径是登录页面的路径,说明登录页可以正常访问,即已经登录过了,那么就正常访问
else{
next();
}
})
export default router
BlogView.vue
<template>
<div>
<ul>
<li>
<router-link to="">JavaScript教程</router-link>
</li>
<li>
<router-link to="">JavaScript教程</router-link>
</li>
<li>
<router-link to="">JavaScript教程</router-link>
</li>
</ul>
</div>
</template>
LoginView.vue
<template>
<div>
<form @submit.prevent="doLogin">
<input type="text" v-model="username">
<input type="text">
<button>登录</button>
</form>
</div>
</template>
<script>
export default{
data(){
return {
username:""
}
},
methods:{
doLogin(){
console.log(this.username);
//本地存储:localStorage对象有一个方法setItem,可以将username的值赋给变量usr(自定义名称),从而达到将username的值存起来的效果
//然后可以在其他页面通过localStorage.getIem("usr")拿到username的值
localStorage.setItem("usr",this.username);
//Vue的路由提供一个编程式导航功能:先获取router对象,再用push("路径")设置跳转页面的路径
//跳转到首页
this.$router.push("/")
}
}
}
</script>
HomeView.vue
<template>
<div class="home">
<h1>首页</h1>
</div>
</template>
<script>
export default {
}
</script>
VideoView.vue
<template>
<div>
<h1>视频</h1>
<video src="" controls></video>
</div>
</template>
九、ElementUI
是一个基于Vue的UI框架,还有其他的基于Vue的UI框架,如:vux,vant
可查看网站https://element.eleme.cn/#/zh-CN获取详细资料
1.引入ElementUI
安装:npm install --save element-ui
在main.js文件中:
引入样式:import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
加载插件:Vue.use(ElementUI);
2.组件
按钮、表单、表格
eg.完成一个学生信息管理功能,包括学员信息的查询、添加、删除功能,要求如下:
后台使用koa实现数据接口
添加功能使用对话框弹出表单(Dialog)
删除需要有确认提示(MessageBox)
APP.vue
<template>
<div>
<!-- dialogVisible是控制对话框是否隐藏的变量 -->
<el-button type="text" @click="dialogVisible = true">添加学生</el-button>
<!-- data绑定了一个数组 -->
<!-- 表格 -->
<el-table :data="studentList" border style="width: 100%">
<el-table-column prop="id" label="ID" width="120">
</el-table-column>
<el-table-column prop="name" label="姓名" width="120">
</el-table-column>
<el-table-column prop="age" label="年龄" width="120">
</el-table-column>
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button @click="del(scope.row.id)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 对话框 -->
<el-dialog title="添加学生信息" :visible.sync="dialogVisible" width="30%" >
<el-form>
<el-form-item label="ID">
<el-input v-model="form.id"></el-input>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="onSubmit">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import axios from "axios"
export default {
methods: {
onSubmit(){
axios.post("http://127.0.0.1:3000/student",{
student:this.form
}).then(()=>{
//重新获取列表
this.getStudentList();
// 关闭对话框
this.dialogVisible=false;
})
},
del(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
axios.delete(`http://127.0.0.1:3000/student/${id}`).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
this.getStudentList();
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
getStudentList() {
axios.get("http://127.0.0.1:3000/student").then((res) => {
this.studentList = res.data;
})
}
},
data() {
return {
studentList: [],
//设置对话框可见
dialogVisible:false,
// 表单
form:{
id:"",
name:"",
age:""
}
}
},
created() {
this.getStudentList();
}
}
</script>
server-ele.js
const Koa=require("koa");
const router=require("koa-router")();//设置路由
const parser=require("koa-parser");//获取post请求数据
// 安装cnpm install --save koa2-cors
const cors=require('koa2-cors');//允许跨域
const static=require("koa-static");
const app=new Koa();
app.use(cors());
app.use(parser());
app.use(static(__dirname+"public"));
app.use(router.routes());
//模拟数据库
const studentList=[
{id:1,name:"小明","age":2},
{id:2,name:"小红","age":4},
{id:3,name:"小亮","age":6},
];
router.get("/student",async ctx=>{
ctx.body=studentList;
})
router.post("/student",async ctx=>{
let student=ctx.request.body.student;
studentList.push(student);
ctx.body=true;
})
router.delete("/student/:id",async ctx=>{
let id=ctx.params.id;
//map遍历,通过id实现删除功能
studentList.map((item,index)=>{
if(item.id==id){
studentList.splice(index,1);
}
})
ctx.body=true;
})
app.listen("3000",()=>{
console.log("server is running");
})
十、项目部署
1.基于Vue的web项目
进入Vue文件夹,通过命令npm run build将VUe项目打包成一个静态文件,会在该文件夹里生成一个dist文件夹,将文件夹里的文件全部拷贝到public即可通过服务器端口访问vue设置的页面然后在服务器中发布
将静态html通过koa发布到服务器上
当项目的开发阶段和发布阶段的阈值不同时,可以通过配置文件实现阈值的切换
配置文件:
.env.development开发阶段配置文件
.env.production生产环境的配置文件,即发布后的
注意:命名变量必须以VUE_APP_XXXX命名
eg.
在.env.production文件:
VUE_APP_TITLE="生产环境"
在.env.development文件:
VUE_APP_TITLE="开发环境"
在APP.vue文件中引入该内容:
<template><div id="app">
<h1>{{ title }}</h1>
</div>
</template>
<script>
export default{
data(){
return{
title:process.env.VUE_APP_TITLE
}
}
}
</script>
再输入命令npm run build生成dist文件,将该文件的内容拷贝到public文件内,然后运行js文件、vue文件
效果如下:
在8080端口(开发阶段):
![]()
而在服务器设置的端口(发布阶段)
![]()
2.实践
在vue文件夹里创建两个配置文件,分别为:
.env.development
.env.production
主要文件结构如下:
·project
·vue
·dist
·src
·APP.vue
·.env.development
·.env.production
·server.js
·public
在.env.development文件中写入:VUE_APP_BASE_URL="http://127.0.0.1:3000"
在.env.production文件中写入:
VUE_APP_BASE_URL=""
将原来APP.vue文件中,路径改为:
然后在vue文件目录下,输入命令npm run build产生dist文件,再将该文件夹下的文件全部拷贝至public文件下,然后分别运行js文件和vue文件,最后即可在服务器里设置的端口(127.0.0.1:3000)打开vun创建的页面,并实现相关功能