Java学习笔记-全栈-web开发-24-Vue


1. 开发环境 VSCode

插件安装

  • Chinese Language
  • ESlint :严格语法工具
  • Live Server :vue的双向数据绑定,如果只是静态的html文件无法看到效果,开启服务器插件
  • open in browser :右键通过浏览器查看
  • Vetur:Vue的代码提示、高亮

前端代码太多缩进,建议这里设置成2个空格。
在这里插入图片描述
跨平台方案(一次代码,多平台使用):
Vue->Weex->uniapp

2. vue介绍

2.1 vue与原生js(或jquery)的优势

jq利用DOM操作,降低了ajax请求函数的复用性(js与html耦合性太高)。
而vue通过框架提供的指令实现数据双向绑定,我们只需要关注业务逻辑,而不需要直接操作DOM元素。

2.2 MVVM

MVVM是前端视图层的分层,(仅相当于MVC中的V);
将视图层分为:Model,View,ViewModel

如果将MVC和MVVM结合,如下图(MVC中的M和V与MVVM中的MV不是一个东西)
在这里插入图片描述

model 是数据, data
view 是模板
vm 是 vm = new Vue();
vm 用了连接数据和视图, 视图的输入框绑定了v-model, 用户输入后会改变data;
model改变也会同步视图更新相关的依赖, 双向绑定就是vm起了作用

Vue与后端模板引擎的区别就在于:
后端模板引擎从后端控制器的Model中获取数据,然后通过形如th:text="${}"的指令将数据渲染到html中。
Vue从JavaScript中获取数据,然后通过形如v-text="msg"的指令将数据绑定到html中。

3. 常用指令

{{}} v-text v-html

{{}}只替换占位符,v-text会替换标签中的内容。{{}}中可以写js代码
v-html的v-text的区别就在于v-html会解析html代码;

如果网速比较慢,{{}}替换之前会一闪而过。解决办法如下
<style>
[v-clock]{
	display:none;
}
</style>

title属性 v-bind v-on

设置title属性的标签,鼠标放上去后会浮出title。

v-bind用于与js中的vue实例中的data中的数据进行绑定(绑定的对象不能是普通字符串),可以缩写为冒号:

v-bind可以绑定为任意原有html属性值,如src、style等等

<a :href="url"></a>
<a :class="flag?'red':'blue'"></a>

也用于使行内可以使用js代码(参考vue中的css)

v-on用于与与js中的vue实例中的methods中的方法进行绑定,可以缩写为@

数据双向绑定 v-model

<body>
	<div>
	<h1>{{msg}}</h1>
	<input type="text" v-model="msg">
	</div>
</body>

<script>
	var vue = new Vue({
		el:'#app',//绑定目标
		data:{
			msg:'我是msg'
		}
	})
</script>

当我们在input中输入数据的时候,h1内的数据也会改变。
最重要的是,其实vue实例中的msg也被修改了

v-model实现了表单元素和model中数据的双向绑定
但是v-model只能用于表单元素,如果将其使用到其他标签上,则v-model就是自定义指令了==

v-bind只能实现单向绑定,model中的(vue实例中的数据)数据被更改时,v-bind绑定位置也会实时修改,但是v-bind绑定的位置发生变化时,model中的数据不会发生变化
即:v-model可实现html修改js数据,v-bind只能实现js修改html数据。


v-model.number能够限制数据为number
v-model.trim能够去除前后空格
v-model.lazy懒加载,对于输入框只有按回车或者onblur(失去焦点)才会同步更新

双向绑定小案例-计算器

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="number1">
    <select v-model="opt">
      <option value="+">+</option>
      <option value="-">-</option>
      <option value="x">x</option>
      <option value="÷">÷</option>
    </select>
    <input type="text" v-model="number2">
    <button @click="handel">=</button> <!--如果无参,方法可以不加括号-->
    <input type="text" v-model="result">


  </div>
  
</body>
<script src="js/vue.js"></script>
<script>
  var vue = new Vue({
    el:'#app',
    data:{
      number1:0,
      number2:0,
      result:0,
      opt:'+',
    },
    methods:{
      handel(){
        if(this.opt=="+"){
          this.result=parseInt(this.number1)+parseInt(this.number2);
        }else if (this.opt="-") {
          this.result=parseInt(this.number1)-parseInt(this.number2);
        }else if (this.opt="x") {
          this.result=parseInt(this.number1)*parseInt(this.number2);
        }else if (this.opt="÷") {
          this.result=parseInt(this.number1)/parseInt(this.number2);
      }
      }
    }
  })

</script>
</html>

Vue中常用的css

red,big,size是通过类名定义好的style
flag是vue实例中的data字段

<div class="red">传统用法</div>
<div :class="['red','big']">通过bind使引号中可以写js代码</div>
<div :class="flag?'size':'big'">通过bind使引号中可以写js代码使用三元运算符</div>
<div :class="'size'=true">命名默认为Bool值,设为true则生效</div>
<div :class="myStyle">通过bind使引号中可以写js代码使用三元运算符</div>

<script>
	var vue=new Vue({
		data:{
			myStyle:{'red':true,'size':true,'big':true}
		}
		
	})
</script>

v-for

simpleList是vue实例中的data中的一个数组。

<p v-for="{item,index} in simpleList">
索引:{{index}}
数据:{{item}}
</p>

index可要可不要
simplelist里面可以是键值对等其他复杂类型,然后通过.运算符访问其内的数据即可。

如下图:
在这里插入图片描述

<p v-for="(obj,i) in objectList">
{{obj.id}}
{{obj.name}}
</p>

数字range

<p v-for="i in 10"></p>

会生成从1到10(而不是从0到9)

注意
建议给每个v-for都加上:key,且key不绑定默认生成的index
因为:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="id">
    <input type="text" v-model="name">

    <p v-for="(obj,i) in objectList">
      <input type="checkbox"> id:{{obj.id}}----name:{{obj.name}}
    </p>
    <button @click='handel'>添加</button>
  </div>
  
</body>
<script src="js/vue.js"></script>
<script>
  var vue = new Vue({
    el:'#app',
    data:{
      id:'',
      name:'',
      objectList:[
        {id:1,name:'张三'},
        {id:2,name:'张四'},
        {id:3,name:'张五'},
        {id:4,name:'张六'},
      ]
        
    },
    methods:{
      handel(){
        var obj={id:this.id,name:this.name}
        this.objectList.unshift(obj);
      }
    }
  })

</script>
</html>

如果先选中任意一个,比如张三,当你通过unshift添加一个新的到前面时,选中框会滑到张四(向下滑落)
绑定key就不会出现这个问题。如果绑定的key是自带的index,也会出现这个问题


v-for="(item,i)in objList"会报错,因为in前面没有空格

v-if v-show

v-if和v-show控制标签显示时,v-if会将渲染页面的标签直接进行删除/添加操作,而v-show是更改标签的display属性。

v-show一定会渲染,v-if则不一定,因此,如果用户可能根本就看不到这个,则用v-if。

v-if后可以用v-else和v-else-if,用法类似

自定义指令

new Vue({
	el:
	data:
	directives:{ //自定义指令
		change:{ //指令名称
			bind:function(){},  //指令绑定到元素
			update:function(){},  //更新时回调
			unbind:function(){},  //解除时回调
		}
	}
})

4. 表格综合增删应用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    id<input type="text" v-model="id">
    name<input type="text"v-model="name">
    sex<input type="text" v-model="sex">
    <button @click="add">添加</button>
    <table v-for="(obj,index) in objectList" :key="obj.id">
        <tr>
            <td>ID</td>
            <td>name</td>
            <td>sex</td>
            <td>操作</td>
          </tr>
      <tr>
        <td>{{obj.id}}</td>
        <td>{{obj.name}}</td>
        <td>{{obj.sex=='1'?'男':'女'}}</td>
        <td><button type="button" @click="deleteUser(obj.id)">删除</button></td>
      </tr>    
    </table>

  </div>
  
</body>
<script src="js/vue.js"></script>
<script>
  var vue = new Vue({
    el:'#app',
    data:{
      id:'',
      name:'',
      sex:'',
      objectList:[
        {id:1,name:'张三',sex:'1'},
        {id:2,name:'张四',sex:'1'},
        {id:3,name:'张五',sex:'0'},
        {id:4,name:'张六',sex:'0'},
      ]
        
    },
    methods:{
      add(){
        var obj={id:this.id,name:this.name,sex:this.sex}
        this.objectList.push(obj);
      },
      deleteUser(objId){
        let index = this.objectList.findIndex(item => {
          if(item.id===objId){
            return true;
          }
        });
        this.objectList.splice(index,1) //从index位置删除一个元素
      },
    }
  })

</script>
</html>

5. vue过滤器

就像是:将参数传入一个filter管道(函数),出来管道的返回值。


<td>{{obj.sex | myfilter}}</td>

<script>
var vue = new Vue({
	filters:{
		myfilter(sex){
			if(sex=='1'){
				return '男';
			}else{
				return 'nv';
			}
		}
	}
})

</script>

6. vue生命周期

在这里插入图片描述几个重要点:
created:methods和data的最早使用函数
beforeMount:尚未挂载(渲染)模板
mounted:操作DOM的最早时刻(此时模板已经渲染了,由于Vue实际不用操作DOM,因此此时是进行页面渲染之后的操作)
剩下的方法图中已经说明的很清楚了;

注意,并没有“正在”这个时间段,只有before=>之前,-ed=>之后。

7. vue组件(核心)

模块化:从代码角度分析的,方便代码分层开发,保证每个模块功能单一
组件化:从UI角度分析的,如分页组件、轮播图组件。

7.1 extend创建组件

注意,script中使用驼峰命名,在html中必须改成小写,且用 - 连接

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>extend创建组件</title>
</head>
<body>
  <div id="app">
    <!-- 注意,script中使用驼峰命名,在html中必须改成小写,且用 - 连接 -->
    <my-component></my-component>
  </div>
  
</body>
<script src="js/vue.js"></script>
<script>

  //创建
  var myComponent = Vue.extend({
    template:'<h1>这是用extend创建的组件</h3>'
  });
  //声明
  Vue.component('myComponent',myComponent);


  var vue = new Vue({
    el:'#app',
    data:{
        
    },
  })

</script>
</html>

声明和创建可以合并

  Vue.component('myComponent',Vue.extend({
    template:'<h1>这是用extend创建的组件</h3>'
  }));

extend也可以省略

  Vue.component('myComponent',{
    template:'<h1>这是用extend创建的组件</h3>'
  });

7.2 使用template创建组件(重点)

  1. 创建vue实例,绑定一个el标签
  2. 创建template标签,给template一个id(注意不是给template中的根节点给id)
  3. 声明Vue.component,绑定template的ID
  4. 在vue实例绑定的el中使用template
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>template</title>
</head>
<body>
  <div id="app">
    <my-Component></my-Component>
  </div>
  
  <template id="mytemp">
    <div>
      <p>hhh</p>
      <h1>big head</h1>
    </div>
  </template>

</body>
<script src="js/vue.js"></script>
<script>

  Vue.component('myComponent',{
    template:`#mytemp`
  })


  var vue = new Vue({
    el:'#app',
    data:{
        
    },
  })

</script>
</html>

组件本身也是一个对象,因此可以直接创建组件对象

var login={
	template:'<p>组件对象</p>'
}

7.3 组件中的data和methods

首先是data,注意,组件的存在是为了提高复用性,而data:{}的写法,会导致多个组件共用一个data,提高了耦合性。因此,组件中的data,必须是一个函数,并且返回一个对象
例子:为什么data必须是函数

Vue.component('mycomponent',{
	template:'#temp1',
	template:'#temp2',
	data(){
		return {
		msg:0
		}
	}
})

如果使用这个,则temp1和temp2都共用

组件中的methods跟vue实例中的相同。

7.4 私有组件

将组件定义在vue实例中,则只有与vue实例绑定的el才能使用该组件

<div id="d1">
<!--这个生效-->
<my-comp></my-comp>
</div>
<!--这个不会生效-->
<div id="d2">
<my-comp></my-comp>
</div>

<template id="temp">私有temp</template>

<script>

var vue = new Vue({
	el:'#d1',
	components:{
		'myComp':{
			template:'#temp'
		}
	}

})
</script>

注意自定义component标签的名字需要用引号

7.5 组件切换

利用v-if切换(两三个组件间的切换)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="flag=true">登录</button>
    <button @click="flag=false">注册</button>
    <login v-if="flag"></login>
    <register v-else></register>
  </div>
  
  <template id="login">
    <div>登录组件
    </div>
  </template>

  
  <template id="register">
      <div>注册组件
      </div>
    </template>
</body>
<script src="js/vue.js"></script>
<script>

  Vue.component('login',{
    template:`#login`
  })

  
  Vue.component('register',{
    template:`#register`
  })

  var vue = new Vue({
    el:'#app',
    data:{
        flag:true,
    },
  })

</script>
</html>

更一般的切换方式(重要)

利用占位符component标签绑定component的名字,然后通过点击更改component名字属性实现组件切换。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="componentName='login'">登录</button>
    <button @click="componentName='register'">注册</button>
    
    <!-- 这个component只是占位符,通过:is绑定真正的component -->
    <component :is="componentName"></component>
  </div>
  
  <template id="login">
    <div>登录组件
    </div>
  </template>

  
  <template id="register">
      <div>注册组件
      </div>
    </template>
</body>
<script src="js/vue.js"></script>
<script>

  Vue.component('login',{
    template:`#login`
  })

  
  Vue.component('register',{
    template:`#register`
  })

  var vue = new Vue({
    el:'#app',
    data:{
        componentName:'login'
    },
  })

</script>
</html>

7.6 组件间的数据传递

父组件向子组件传递属性

vue实例是一个父组件,先看vue实例中的数据如何传递给vue实例中的私有组件(即子组件)。
子组件中有一个专门用于访问父组件属性的一个对象:props

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <my-component :parent-msg="msg"></my-component>
  </div>

  <template id="temp">
    <div>
      <p>{{msg}}</p>
      <!-- 这个不会显示 -->
      <p>{{parentMsg}}</p>
    </div>
  </template>

</body>
<script src="js/vue.js"></script>
<script>

  var vue = new Vue({
    el:'#app',
    data:{
      msg:'我是父组件中的数据'
    },
    components:{
      'my-component':{
        template:'#temp',
        data(){
          return {
            msg:'我是子组件中的数据'
          }
        },
        props:{
          'parent-msg':{
              type:String,
              default:null
          }
        }
      }
    }
  })

</script>
</html>

关键逻辑:

  • 在私有组件(子组件)的props中声明要使用一个属性名parent-msg
  • 在子组件html代码中使用该属性名{{parentMsg}} 双花括号中改成驼峰
  • 在组件调用的地方进行数据绑定:将parentMsg绑定到父组件属性msg中 ----> :parent-msg=“msg”

props可以定义为数组props:[‘parentMsg’],当它定义为对象时,是标准写法。

注意凡是标签内的驼峰式都改成-连接,凡是{{}}中的都改成驼峰

props中的数据是只读的,不要用子组件去更改父组件中的数据

父组件向子组件传递方法

根据父组件向子组件传递属性的做法,与传递属性有点不太一样,这个比较绕

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p>{{msg}}</p>
    <my-component @parent-change="changeMsg"></my-component>
  </div>

  <template id="temp">
    <div>
      <button @click="there_is_son_method">修改</button>
    </div>
  </template>

</body>
<script src="js/vue.js"></script>
<script>

  var vue = new Vue({
    el:'#app',
    data:{
      msg:'我是父组件中的数据'
    },
    methods:{
      changeMsg(){
        this.msg="被修改了"
      }
    },
    components:{
      'my-component':{
        template:'#temp',
        methods:{
          there_is_son_method(){
            this.$emit('parent-change')
          }
        }
      }
    }
  })

</script>
</html>

关键步骤

  • 子组件中声明一个方法there_is_son_method
  • 子组件html代码中调用这个方法
  • 这个方法体中使用$emit声明使用父组件中的方法,假定命名为parent-change(这个并没有直接引用到父组件方法中,可以理解为:vue假定父组件对子组件是不可见的,因此此处不能直接引用父组件)
  • 在组件被调用处,将parent-change与父组件中的方法绑定起来

(个人理解)父子组件间的数据传递,都是通过子组件声明父组件属性/方法,然后在html代码中实现父子属性/方法的绑定

$emit

$emit表示声明使用父组件的方法,第一个参数是假定的父组件名称,后面的都是参数

父组件调用子组件的方法

首先学习ref


下面这个是js的写法,操作dom元素

<div id="div1"></div>
<script>
let data = document.getElementById("div1").innerText;
</script>

下面是vue操作dom元素的方法

<div ref="div1"></div>
<script>
let data = this.$refs.div1.innerText
</script>

可以让父组件通过ref找到子组件,然后调用子组件的方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="parentFunc">父组件调用子组件</button>
    <my-component ref="refname"></my-component>
  </div>

  <template id="temp">
  </template>

</body>
<script src="js/vue.js"></script>
<script>

  var vue = new Vue({
    el:'#app',
    methods:{
      parentFunc(){
        console.log("父组件方法")
        this.$refs.refname.childFunc()
      }
    },
    components:{
      'my-component':{
        template:'#temp',
        methods:{
          childFunc(){
            console.log("子组件被调用")
          }
        }
      }
    }
  })

</script>
</html>

关键步骤:

  • 子组件被调用的地方加上ref属性(即自定义标签名处,而不是声明子组件的template上)
  • 父组件中通过$refs找到该ref的dom元素,然后调用子组件方法

7.7 注意点

  • 组件只有一个属性值,但是属性值中可以写任意html代码,而且,必须只能有一个根节点(最外层必须要有一个标签包裹着)
  • 用``包裹组件属性值,其内如果用到变量,则用${变量名}模板语法。
  • 驼峰与短横线
    • {{}}不能用-,只能用驼峰法
    • 标签命名只能用-连接,不能用驼峰法
    • :子组件引用名=“父组件实际名”,子组件引用名只能用-连接,不能用驼峰法
    • vue认为驼峰写法和短横写法是同一个对象

7.8 小结

在这里插入图片描述

8. 路由

需要使用vue-router包,当导入这个包之后,window全局就有了VueRouter这个构造函数。

什么是路由
后端:对于普通的网站,所有超链接都对应一个url,指向服务器资源
前端:对于单页面应用,主要通过url的#(hash)来实现不同页面的切换,通过hash实现的(相当于a标签)

每个路由是一个对象(用{}定义),每个对象都包含两个属性:path:表示路由的url,component:表示路由的跳转目标组件

8.1 初试路由

步骤:

  • 创建路由对象
  • 将路由对象声明到vue实例中
  • 创建组件
  • html中使用router-view给组件占位
  • html中使用router-link实现组件切换
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">

    <!-- 定制调用路由 -->
    <router-link to="/login" tag="button">登录</router-link>
    <router-link to="/register">注册</router-link>
    <!-- 这是一个占位符,改变路由的时候,会显示指定组件 -->
    <router-view></router-view>
  </div>

  <template id="login"><div>登录组件</div></template>
  <template id="register"><div>注册组件</div></template>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>
  var login = {
    template:'#login'
  }

  var register = {
    template:'#register'
  }

  let routerObj = new VueRouter({
    routes:[
      {path:'/',redirect:'/login'},//这个不是重定向请求,仅仅是重定向组件
      {path:'/login',component:login},
      {path:'/register',component:register},
    ]
  })

  var vue = new Vue({
    el:'#app',
    router:routerObj
  })

</script>
</html>

8.2 路由传参(重点)

前后端未分离时:

  • 页面A被点击,传递id到后端控制器,后端查出数据,转发给页面B,页面B渲染数据

前后端分离(Vue):

  • 页面A被点击,传递id给页面B,页面B在created时期根据id将数据查出,页面B渲染数据。

方法一:使用路由时/login?name=zs&psw=123的形式传参,然后$route.query.name获取
方法二:定义路由时设置 /register/:name/:psw ,使用router-link时/register/ls/123,然后$route.params.name获取

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">

    <!-- 定制调用路由 -->
    <router-link to="/login?name=zs&psw=123" tag="button">登录</router-link>
    <router-link to="/register/ls/123">注册</router-link>
    <!-- 这是一个占位符,改变路由的时候,会显示指定组件 -->
    <router-view></router-view>
  </div>

  <template id="login"><div>登录组件
    <P>{{$route.query.name}}</P>
    <P>{{$route.query.psw}}</P>
  </div></template>
  <template id="register"><div>注册组件
    <p>{{$route.params.name}}</p>
    <p>{{$route.params.psw}}</p>
  </div></template>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>
  var login = {
    template:'#login'
  }

  var register = {
    template:'#register'
  }

  let routerObj = new VueRouter({
    routes:[
      {path:'/',redirect:'/login'},//这个不是重定向请求,仅仅是重定向组件
      {path:'/login',component:login},
      {path:'/register/:name/:psw',component:register},
    ]
  })

  var vue = new Vue({
    el:'#app',
    router:routerObj
  })

</script>
</html>

8.3 路由的嵌套

routers里面使用children再声明路由,就是路由嵌套。
children与path同级

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <router-view></router-view>
  </div>

  <template id="index">
    <div>
      <h1>首页</h1>
      <router-link to="index/login" tag="button">点击登录</router-link>
      <router-view></router-view>
    </div>
  </template>

  <template id="login">
    <div>
      <p>登录组件</p>
      <!-- 使用子路由的时候,必需且仅需加上上一级父路由 -->
      <router-link to="login/success" tag="button">提交登录</router-link>
      <router-view></router-view>
    </div>
  </template>

  <template id="success">
    <div>
      <p>登录成功</p>
    </div>
  </template>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>
  var login = {
    template:'#login'
  }

  var success = {
    template:'#success'
  }

  var index = {
    template:'#index'
  }

  let routerObj = new VueRouter({
    routes:[
      {path:'/',redirect:'/index'},//这个不是重定向请求,仅仅是重定向组件
      {path:'/index',component:index,
        children:[{
          // 斜杠/表示从根路径匹配,而子路由不能加/,而且实际上会默认给子路由加上父路由
          path:'login',component:login,
          children:[{
            path:'success',component:success
          }]
        }]
      }]

  })

  var vue = new Vue({
    el:'#app',
    router:routerObj
  })

</script>
</html>

注意问题:

  • children必须是数组(内部实现使用了 for:each遍历)
  • 使用子路由的时候,必需且仅需加上上一级父路由(如上述例子index->login->success,而success只的router-link只需要写“login/success”而不需要写成“index/login/success”)
  • 嵌套路由的跳转,页面会同时显示父路由绑定的template(因为组件中必须带上template占位)

8.4 router和route

router是VueRouter的实例,是全局对象,包含了所有路由的一些关键对象和属性;
route是一个特定路由对象,是局部对象,比如监听器监控路由的时候就是用route,表示监控当前跳转的路由。

8.5 编程式路由(重点)

也就是:通过js实现路由跳转

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    用户名<input type="text" v-model="name"><br>
    密码<input type="password" v-model="psw"><br>
    <button @click="toGetLogin">get登录</button>
    
    <br>
    用户名<input type="text" v-model="name"><br>
    密码<input type="password" v-model="psw"><br>
    <button @click="toPostLogin">Post登录</button>

    <!-- 凡是路由跳转,都得给占位 -->
    <router-view></router-view>
  </div>

  <template id="login"><div>
    <p>登录成功</p><br>
    <P>欢迎使用path+query方式:{{$route.query.name}}</P>
    <P>欢迎使用name+params方式:{{$route.params.name}}</P>
  </div></template>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>
  var login = {
    template:'#login'
  }

  let routerObj = new VueRouter({
    routes:[
      {path:'/',redirect:'/login'},//这个不是重定向请求,仅仅是重定向组件
      {name:'login',path:'/login',component:login},
    ]
  })

  var vue = new Vue({
    el:'#app',
    data:{
      name:'张三',
      psw:'123456'
    },
    methods:{
      toGetLogin(){
        this.$router.push({
          path:'/login',
          query:{
            name:this.name,
            psw:this.psw
          }
        })
      },
      toPostLogin(){
        this.$router.push({
          name:'login',
          params:{
            name:this.name,
            psw:this.psw
          }
        })
      }
    },
    router:routerObj
  })

</script>
</html>

知识点:

  • query传参,方法中只需要path,浏览器的url中可以看到?&的参数
  • params传参,方法必须使用name,浏览器中的url看不见参数
  • params不能和path同时使用

9. 名称拼接数据绑定

假设有对象:firstname、lastname、fullname,如何实现:当firstname和lastname改变时,同步改变fullname?

data中不能直接实现fullname:firstname+lastname

9.1 监听器

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>firstname: {{firstname}}</div>
    <div>lastname: {{lastname}}</div>
    <div>fullname: {{fullname}}</div>
    <input type="text" v-model="firstname">
    <br>
    <input type="text" v-model="lastname">
    <br>
    <input type="text" v-model="fullname">
  </div>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>


  var vue = new Vue({
    el:'#app',
    data:{
      firstname:'',
      lastname:'',
      fullname:'',
    },
    watch:{
      firstname(newVal,oldVal){  //监听器默认参数,第一个表示监听对象的新值,第二个为旧值
        this.fullname = newVal+this.lastname
      },
      lastname(newVal,oldVal){
        this.fullname = this.firstname+newVal
      }
    }
  })

</script>
</html>

凡是’firstname’:funtion(){}都可以简写为firstname(){}

使用监听器的好处:

  • 开发者只需要关注model之间的关系,而不需要关注用户的操作对view的影响。

监听路由

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <router-view></router-view>
  </div>

  <template id="index">
    <div>
      <h1>首页</h1>
      <router-link to="index/login" tag="button">点击登录</router-link>
      <router-view></router-view>
    </div>
  </template>

  <template id="login">
    <div>
      <p>登录组件</p>
      <!-- 使用子路由的时候,必需且仅需加上上一级父路由 -->
      <router-link to="login/success" tag="button">提交登录</router-link>
      <router-view></router-view>
    </div>
  </template>

  <template id="success">
    <div>
      <p>登录成功</p>
    </div>
  </template>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>
  var login = {
    template:'#login'
  }

  var success = {
    template:'#success'
  }

  var index = {
    template:'#index'
  }

  let routerObj = new VueRouter({
    routes:[
      {path:'/',redirect:'/index'},//这个不是重定向请求,仅仅是重定向组件
      {path:'/index',component:index,
        children:[{
          // 斜杠/表示从根路径匹配,而子路由不能加/,而且实际上会默认给子路由加上父路由
          path:'login',component:login,
          children:[{
            path:'success',component:success
          }]
        }]
      }]

  })

  var vue = new Vue({
    el:'#app',
    router:routerObj,
    watch:{
      '$route.fullPath'(newVal,oldVal){
        console.log('用户从',oldVal,'跳转到',newVal)
      }
    }
  })

</script>
</html>

注意看watch部分

在这里插入图片描述
通过路由监听,可以实现后端拦截器的功能:监听路由的url是否为登录url,如果不是,则检查用户是否登录过,若没登陆过,则重定向到登录路由。

9.2 computed

用监听器实现数据拼接太麻烦了,有更好的方式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>firstname: {{firstname}}</div>
    <div>lastname: {{lastname}}</div>
    <div>fullname: {{sumName}}</div>
    <input type="text" v-model="firstname">
    <br>
    <input type="text" v-model="lastname">
    <br>
    <input type="text" v-model="sumName">
  </div>

</body>
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<script>


  var vue = new Vue({
    el:'#app',
    data:{
      firstname:'',
      lastname:'',
      fullname:'',
    },
    computed:{
      sumName(){
        return this.firstname+this.lastname
      }
    }
  })

</script>
</html>

注意:

  • 虽然computed中定义的是方法,但是它的使用方式,一定是属性,决不能带括号
  • 计算属性中的任意属性变化了,则会立即进行计算
  • 计算属性的结果会被缓存,计算属性中的属性没有改变时,不会进行计算

computed的get和set

还可以将computed当做对象使用,调用他的get和set方法。
get:当计算computed目标时调用
set:当设置computed目标时调用

需求:
计算器a+b=c,c为计算属性,要求更改ab时,得出c----get。
要求更改c的时候,得出a和b----set

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model.number="a">
    +
    <input type="text" v-model.number="b">
    =
    <input type="text" v-model.number="sum">
  </div>

</body>
<script src="js/vue.js"></script>
<script>


  var vue = new Vue({
    el:'#app',
    data:{
      a:0,
      b:0,
      c:0,
    },
    computed:{
      sum:{
        get(){
          return this.a + this.b
        },
        set(val){
          this.a=val-this.b;
          this.b=val-this.a;
        }
      }
    }
  })

</script>
</html>

10. Webpack

10.1 准备工作 – node 和 npm

node是JavaScript的运行时环境(类似于Tomcat是Java的一个运行环境),node为js提供了更强大的操作方式,
如:

  • 在浏览器中,js无法写服务接口,node提供了后端代码编写功能(写后台、操作数据库)
  • 在浏览器中,js无法操作文件,node提供了文件操作

但不会真的用node写后台,更多的是用它来写前端配置,如:跨域代理

此处node仅用于提供编写vue的环境。

初步使用,官网下载node,一路下一步即可。然后将nodejs整个文件夹加入到环境变量中

然后在cmd中查看node是否配置成功

node -v

并查看npm版本

npm -v

npm是node提供的一个包管理工具,类似于maven。
通过npm安装依赖包,就不需要在页面上使用script标签引入了。

npm配置淘宝镜像:

npm config set registry https://registry.npm.taobao.org

10.2 开始webpack

问:网页中的静态文件过多会怎样?
答:静态文件需要发起二次请求来引入,过多的时候会导致网页很慢很卡
问:网页中有哪些静态文件?
答:js、css、图片、

如何解决上述问题?
合并、压缩、精灵图(将小图合并为一张图,因此只需要请求一次)、base64编码、webpack

概念理解(配置文件中)

  • 入口(entry):webpack应该从哪个地方开始进行打包
  • 输出(output):打包之后的路径,打包之后会输出bundles,默认为./dist(因此可以不配置)
  • loader:让webpack能够处理非js文件,webpack默认只能处理js文件
    • test属性:用于标识被对应的loader进行转换的某些文件(正则表达式)
    • use属性:标识转换时使用哪个loader
  • 插件(plugins):常用于打包优化和压缩,使用某个插件,通过==const pluginName = require(‘插件名’)==引入
  • mode:development或者production,设置开发环境(调试编码)还是生产环境(上线)

import和export

import 导入
export导出
vue不像java,需要使用某个模块时,不仅仅需要import导入该模块,而且需要在该模块中将所需对象export向外暴露,否则import是无法生效的。
export default一个模块有且仅有一个

10.3 创建webpack项目(学习)

  1. 创建一个空文件夹webpackStudy,用VSCode打开该文件夹

  2. 打开cmd终端,执行npm init,后面出现的各种初始化信息按需选择,我只填了个test项目名,其中默认的入口文件是index.js,然后一直回车到底,生成对应的package.json文件,用于打包配置。

  3. 安装webpack,先全局环境,后开发环境(可以先用webpack -v查看全局是否安装过)

    webpack -v
    npm i webpack -g
    npm i webpack --save-dev
    

    可以看到package.json中的devDependencies已经导入了webpack

  4. 创建src文件夹,(src表示源码目录),在scr下创建入口文件index.js和index.html (node_modules是node的依赖包,不要管也不要动)

    alert('我是index');
    

    我用的 !+回车 自动生成的html代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>index</title>
    </head>
    <body>
      <h1>我是首页</h1>
    </body>
    </html>
    
  5. 根目录创建webpack.config.js文件

    //导入内置的路径处理模块
    const path = require('path');
    
    const config = {
      entry: path.resolve(__dirname,'src/index.js'),
      output: {
        // path: path.resolve(__dirname,'dist'),//输出路径,默认就是dist
        filename:'bundle.js' //输出文件名,默认index.js
      
      }, 
    };
    
    module.exports = config;
    

    意思是配置入口文件,

  6. 更改终端启动命令,在package.js中将scripts修改为如下

    {
      "name": "test",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "dev":"webpack"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "webpack": "^4.41.5"
      }
    }
    

    scripts用于配置npm run命令,上述更改意思是当npm run dev时,等价于npm run webpack,简化代码,可以自定义

  7. 执行npm run dev查看效果,控制台会要求下载webpack-cli,输入yes进行安装。随后可以看到多了dist文件夹,里面有打包好的main.js,但是,并没有将index.html打包,因为webpack只支持js,因此需要插件

  8. 安装webpack生成html文件的插件:

    npm i html-webpack-plugin --save-dev
    

    在webpack.config.js中声明使用该插件

    //导入内置的路径处理模块
    const path = require('path');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    
    const config = {
      entry: path.resolve(__dirname,'src/index.js'),
      output: {
        // path: path.resolve(__dirname,'dist'),//输出路径,默认就是dist
        filename:'bundle.js' //输出文件名,默认index.js
      
      }, 
      plugins: [
        new HtmlWebpackPlugin({
          template: path.resolve(__dirname,'src/index.html'),
        })
      ],
    };
    
    module.exports = config;
    
    

    然后npm run dev,可以看到dist下生成了index.html,打开该index,能够看到“我是首页”的h1标题,也能看到alert弹窗

  9. 配置实施热部署
    安装 npm i webpack-dev-server --save-dev
    将原本package中的scripts中的"dev":"webpack-dev-server --open --hot " 后面还可以跟 --port 8888实现更改端口,默认8080
    运行npm run dev
    注意webpack-dev-server不会实现实时打包,而是存放在内存中,当编码完毕再执行webpack进行打包

  10. 配置打包css文件:
    给index.html中的h1加一个class=“my”,在src下创建css/index.css,里面写

    .my{
      color: red
    }
    

    在index引入这个css文件

    alert('我是index');
    
    import './css/index.css'
    

    然后运行项目会发现没作用,打开控制台报了一堆错误,还是那句话:因为webpack只支持js。因此需要安装loader

    npm i --save-dev style-loader css-loader
    

    在webpack.config.js中创建models节点,装载loader

    //导入内置的路径处理模块
    const path = require('path');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    
    const config = {
      entry: path.resolve(__dirname,'src/index.js'),
      output: {
        // path: path.resolve(__dirname,'dist'),//输出路径,默认就是dist
        filename:'bundle.js' //输出文件名,默认index.js
      
      }, 
      plugins: [
        new HtmlWebpackPlugin({
          template: path.resolve(__dirname,'src/index.html'),
        })
      ],
      module:{//注册第三方模块
        rules:[
          {test: /\.css$/,use: ['style-loader','css-loader']}//配置处理css
        ]
      }
    };
    
    module.exports = config;
    

    然后运行项目发现css起作用了。
    (如果是UI框架,则直接在index.html中导入即可)

  11. 开始vue编写,将index.js中的代码全部注释
    安装vue和loader

    npm -i vue vue-loader vue-template-compiler --save-dev
    

    在webpack.config.js中注册组件和loader(仅仅展示了关键代码)

    const VueLoaderPlugin = require('vue-loader/lib/plugin');
    
    plugins: [
        new HtmlWebpackPlugin({
          template: path.resolve(__dirname,'src/index.html'),
        }),
        new VueLoaderPlugin(),
      ],
      module:{//注册第三方模块
        rules:[
          {test: /\.css$/,use: ['style-loader','css-loader']},//配置处理css
          {test: /\.vue$/,use: 'vue-loader'},//配置处理vue
        ]
      }
    

    index.html中给一个vue容器

    <div id="app"></div>
    

    创建template/login.vue

    <template>
      <div>
        <h1>登录组件</h1>
        <div>{{msg}}</div>
      </div>
    </template>
    
    <script>
    export default{
      data(){
        return{
          msg: 'Hello login'
        }
      },
      methods:{
    
      },
      filters:{
    
      },
      computed:{
    
      }
    }
    </script>
    
    <style scoped>
    /* 必须加scoped,表示仅在本组件中生效,否则会产生样式污染 */
    </style>
    

    在index.js中导入

    import Vue from 'vue/dist/vue.esm.js' 
    import login from './templates/login.vue'
    
    var vm = new Vue({
      el: '#app',
      rander: c => c(login),  //意思是,传入的是一个函数c,返回值是c(login),注册组件并渲染
    })
    
  12. vue路由的使用:
    安装路由
    npm i vue-router --save-dev
    src下创建router/index.js

    import Vue from 'vue/dist/vue.esm.js'
    import login from '../templates/login.vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    var router = new VueRouter({
      routes:[
        {path: '/login', component: login}
      ]
    })
    
    export default router
    

    src下的index.js导入该路由

    import Vue from 'vue/dist/vue.esm.js' 
    import app from './app.vue'
    
    import router from './router'
    
    
    var vm = new Vue({
      el: '#app',
      rander: c => c(app),  //意思是,传入的是一个函数c,返回值是c(app),注册组件并渲染
      router: router,
    })
    

11. vue-cli(核心)

11.1 创建项目

  1. 全局安装 npm i -g @vue/[email protected]
  2. 查看版本 vue -V (V要大写)
  3. 进入到项目目录下,使用vue ui命令打开创建项目窗口
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    然后点击创建项目,不保存预设。
  4. 然后用弹出来的仪表盘,下载相关依赖(注意不是插件)
    我们需要axios和element-ui
    在这里插入图片描述
  5. 用vscode将该项目打开(脚手架在package中的scripts中配置的是serve运行项目,可以将其改成dev)
  6. 根目录创建vue.config.js配置文件
    // vue.config.js 配置说明
    // 这里只列一部分,具体配置参考文档
    module.exports = {
        // baseUrl :'/' 
        // 将部署应用程序的基本URL
        // 默认情况下,Vue CLI假设您的应用程序将部署在域的根目录下。
        // https://www.my-app.com/。如果应用程序部署在子路径上,则需要使用此选项指定子路径。例如,如果您的应用程序部署在https://www.foobar.com/my-app/,集baseUrl到'/my-app/'.
    
        // baseUrl: '/',
    
        //   lintOnSave:{ type:Boolean default:true } 问你是否使用eslint
        lintOnSave: false,
        // productionSourceMap:{ type:Bollean,default:true } 生产源映射
        // 如果您不需要生产时的源映射,那么将此设置为false可以加速生产构建
        productionSourceMap: false,
        // devServer:{type:Object} 3个属性host,port,https
        // 它支持webPack-dev-server的所有选项
    
        devServer: {
            port: 8085, // 端口号
            open: true, //配置自动启动浏览器
            // proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理
            proxy: {
                '/': {
                    target: 'http://localhost:8080',
                    ws: true,
                    changeOrigin: true
                }
            },  // 配置多个代理
        }
    }
    
  7. 然后 npm run dev ,可以看到vue启动的页面
  8. src下创建utils文件夹,创建request.js配置axios,这是一个官方推荐的配置,主要是通过request拦截器在发起请求之前进行加密等处理,通过response拦截器在页面响应请求之前处理数据。
    import axios from 'axios'
    const service = axios.create({
      baseURL: '/',
      timeout: 5000 // 默认请求超时时间5s
    })
    
    // request 拦截器
    service.interceptors.request.use(
      config => {
        return config
      },
      error => {
        return Promise.reject(error)
      }
    )
    
    // response 拦截器
    service.interceptors.response.use(
      response => {
        const res = response.data
        return res
      },
      error => {
      }
    )
    
    export default service
    
    
  9. 在src下创建api文件夹,编写api.js,用于编写api方法,如增删改查
    import request from '../utils/request'
    const group_name = 'department'
    export default {
      departmentList() {   //查询列表接口
        return request({
          //反引号中可以使用模板表达式,可以任意换行
          url: `/${group_name}/departmentList`,
          method: 'get'
        })
      },
      save(department){  //保存接口
        return request({
          url:`/${group_name}/save`,
          method: 'post',
          data:department
        })
      }
    }
    

11.2 element-ui

  1. 将App.vue中的nav和style删除,将view中的vue文件删除,将router的index.js中与home和about页面相关的都注释掉
  2. 引入element-ui,main.js中
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    Vue.use(ElementUI);
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
    
  3. 然后去element-ui官方文档查看示例代码尽情复制修改

11.3 编写步骤

  1. 在component或者view下创建组件(view是页面级组件、component是小组件)
  2. 在router/index.js中导入该组件,并注册路由
  3. 在其他页面引入该路由,通过路由访问该组件;或者在其他页面下import该组件,通过组件形式访问该组件;
方式一:通过路由访问该组件
<router-link to="/login">登录</router-link>
方式二:通过导入组件访问(引入、声明、使用)
<template>
  <div id="login">
    <LoginForm/> 这里使用组件
  </div>
</template>

<script>
// 这里引入组件
import LoginForm from '@/components/LoginForm.vue' 

export default {
  name: 'Login',
  //这里声明组件
  components: {
    LoginForm
  }
}
</script>

12. 跨域

跨域:当请求方和响应方的ip或端口不同时,就是跨域;出于安全起见,浏览器不允许跨域请求

解决方案:

  1. 后端通过过滤器放行 options 请求,设置 Access-Control-Allow-Origin 头部解决跨域(浏览器会先发options请求“探路”)
  2. 使用Nginx做代理,由于跨域问题只出现在客服端对服务端,而服务端对服务端是不存在的,因此通过Nginx实现:客户端请求Nginx,Nginx请求服务端,避免了客户端请求服务端,解决跨域(生产环境
  3. 使用node.js解决跨域(vue中使用webpack解决),就是上面的vue.config.js中的proxy配置,而request.js中的baseURL的/,就是指这个代理地址(适用于开发环境,因为并发能力太差

13. UI框架

PC端:

  • bootstrap:底层依赖jQuery,不符合vue思想
  • layUI:虽然使用时不需要引入jQuery,但是底层使用了jQuery,而且非常依赖DOM操作
  • iView:专职于vue的UI框架
  • ant-design-vue:蚂蚁金服开源的针对vue开发的UI框架,jeecg就是用这个
  • elementUI:目前vue开发者使用最多的UI框架,饿了么(阿里)开源

移动端

  • MUI:半原生开发
  • MintUI:功能较少
  • AntUI:蚂蚁金服开源,支付宝风格,封装不太好
  • vant:封装较好

js重点

var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。

list.push(obj)  将obj添加到数组后面
list.unshift(obj) 将obj添加到数组前面

不要使用关键字给函数命名,如delete

学习一下ES6的语法。

null表示有这个对象,但是为空
undefined表示压根没有这个对象

VSCode编码语法

!回车:生成html框架
select>option4:生成有4个选项的select标签
table>tr
3>td*5:生成三行五列的table

发布了118 篇原创文章 · 获赞 26 · 访问量 8007

猜你喜欢

转载自blog.csdn.net/weixin_43367550/article/details/104071651