前言
尤其是大公司1个部门中可能有擅长Java/Node/Python/Php/Go的,有的擅长同1种后端语言而擅用不同的web框架。
那么如何在不做人员优化的前提下,凝聚起这些不同的研发力量,为1个共同的项目添砖加瓦呢?
我们可以采用前后端分离的开发模式,大家后端都用自己擅长的后端语言玩,但共用1个前端框架。
ECMAScript6的基本语法
Vue是一个渐进式的JavaScript框架。
什么是渐进式呢?
声明式渲染(vue.js):vue最简单的用法,就是在html文件中引入1个vue.js库。
组件系统(component):vue支持组件化开发,可以让我们的前段代码更具扩展性。
客户端路由(vue-router):可以让我们在前段支持路由系统,开发1个单页面应用。
集中式状态管理(vue-x):支持我们在前段保存数据。
项目构建(vue-cli):vue自带的脚手架可以帮我们快速构建和打包一个web项目。
以上是一个渐进开发的过程,我们可以根据自己的项目规模选择使用 或者使用全部功能。
JavaScript和Python一样也有不同的版本,JavaScript 是 ECMAScript 规范的一种实现。
ECMAScript:是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。
let变量声明
ES6中新增了let命令。用来声明变量。它的用法类似于var。
1.let声明的变量只能在局部作用域使用
2.不会存在变量提升
3.变量不能重复声明,变量声明之后可以修改。

<script> var arry1 = []; for (var i = 0; i < 10; i++) { arry1[i] = function () { console.log(i); }; } arry1[6]();//10 var arry1 = []; for (let i = 0; i < 10; i++) { arry1[i] = function () { console.log(i); }; } arry1[6]();//10 {//{}就代表局部作用域 let full_name = "zhanggen"; //let声明过的变量无法重复声明 //let full_name = "zhanggen1"; } //let声明的变量只能在局部作用域生效 //console.log(full_name) </script>
const常量声明
ES6中新增了const命令。用来声明常量。
1.const声明的常量只能在局部作用域生效
2.不会存在变量提升
3.常量不能重复声明,常量声明之后不可以修改。
函数
在ES6中我们可以使用箭头函数。

//es5的函数语法 let add = function (x) { return x + 10 }; console.log(add(20)); //es6的函数语法 箭头函数 let add2 = (x) => { return x + 10 }; console.log(add2(20)); // let add3 =x1=>x1; console.log(add3(30))
类
JavaScript中的原型就是为了实现其他语言中面相对象概念中的继承。
Person.prototype.show_name相当于在person的父类上添加方法,所有继承person类的子类都可会拥有show_name方法。

<script> function Person(name, age) { this.name = name; this.age = age; } //JavaScript中的原型就是为了实现其他语言中面相对象概念中的继承。 // Person.prototype.show_name相当于在person的父类上添加方法,所有继承person类的子类都可会拥有show_name方法。 //基于原型给对象声明方法 Person.prototype.show_name = function () { console.log(this.name) }; //es6中的类 class People { //相当于python类中的__init__方法 constructor(name, age,is_ok = true) { this.name = name; this.age = age; this.isok = is_ok; } showname() { console.log(this.name) } showage() { console.log(this.age) } showhealthy() { console.log(this.isok) } } let p = new People("Martion", 27); p.showhealthy() </script>
对象
在对象声明时 使用ES5语法(function foo(){})或ES6语法单体模式声明的函数(function(){}), this会在指向调用当前function的对象。
如果使用ES6语法箭头函数() => {} 声明function时当前this会发生改变,this会指向调用当前function对象的父级对象。
ES6的箭头函数是一把double-edged sword,有时候也会导致我们混淆this,但有些场景下我们可以利用ES6的箭头函数这1特点。
例如 在Vue对象中的created钩子函数中发ajax()、setinsetInterval()时我们可以使用箭头函数(=>)继续指向Vue对象。

<script> var person = { name: "日天", age: 18, fav: function () { console.log(this);//当前this指向当前对象 console.log(this.name); // } }; person.fav(); var person2 = { name: "日地", age: 23, fav:()=>{ console.log(this);//使用箭头函数当前this发生了改变,指向了定义person2的父级对象(上下文) console.log(this.name); // } }; person2.fav(); //对象单体模式 let person3={ name:"日空气", fav(){ //函数语法更加简单 console.log(this);//当前this依然指向对象本身 console.log(this.name); } }; person3.fav() </script>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>轮播图</title> <script src="./vue.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script> </head> <body> <div id="app"> <img style="width: 200px;height: 200px " :src="images[current_index].imgsrc" alt=""> <br> <button @click="next_one">下一张</button> <button @click="previous_one">上一张</button> </div> </body> <script> new Vue({ el: "#app", data() { return { images: [ {"id": 1, "imgsrc": "./img/1.jpg"}, {"id": 2, "imgsrc": "./img/2.jpg"}, {"id": 3, "imgsrc": "./img/3.jpg"}, ], current_index: 0 } }, methods: { previous_one() { if (this.current_index === 0) { this.current_index = this.images.length - 1 } else { this.current_index-- } }, next_one() { if (this.current_index === this.images.length - 1) { console.log(this.current_index); this.current_index = 0 } else { this.current_index++ } }, }, //组件创建完成时执行的钩子函数 created() { //自动轮播图使用箭头函数(=>)指向Vue对象. setInterval(() => { if (this.current_index === this.images.length - 1) { console.log(this.current_index); this.current_index = 0 } else { this.current_index++ } }, 2000) }, }) </script> </html>
promise语法
promise和axios之间的联系。
1门编程语言出新语法也无非也是出于开发者书写出的代码 更易于理解、清晰、扩展的目的。
promise是什么?
Essentially,a promise is a returened object to which you attach callbacks,instead of passing callbacjs into a function.
我仅读懂懂了这句英文解释,^_^。解决函数套函数再套函数 这种代码书写方式的,那axios基于promse语法实现它就可以一直 .then下去。
promise是1个对象,这个对象有 pending、fullfilled、reject3种状态,它接收1个函数为参数,这函数参数携带resolve和reject 2个参数。
reolve执行触发这个对象then方法执行,reject执行触发这个对象的catch方法。
let promise = new Promise(function (resolve, reject) { resolve('response'); });
如果我们需要从后端发多个异步的ajax请求,它们之间有逻辑关系。我们不能在ajax的success回调函数中逐层嵌套下去,可以使用promise。

new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function (result) { // (**)
setTimeout("alert('对不起, 要你久候')", 000)
console.log(result); // 1
return result * 2;
}).then(function (result) { // (***)
console.log(result); // 2
return result * 2;
}).then(function (result) {
console.log(result); // 4
return result * 2;
}).then(function (result) {
console.log(result) //8
return result * 2
});
MVVM设计模式
MVVM设计模式本质是对MVC和MTV(Django模板语言)设计模式的一种优化。
MVVM是Model-View-ViewModel的简写,是在前/后端分离的前提下,再单独把前端划分为3个部分。
M: 即Model,模型,指的就是数据
V: 即View, 视图,指的就是页面
VM:即View-Model,指的是把模型和视图双向绑定起来,即View和Model互相影响,Model变了,View随之改变;View变了,Model也随之改变
// Vue对象的介绍 const app = new Vue({ el: "#app", // el用于挂载视图,即使用css选择器,选中当前vue可以管理的视图范围,注意: vue选择视图必须使用的是双标签,html和body除外 data:{ // data是一个对象,里面存储的就是视图数据,支持js的各种数据类型(简单 对象 数组) message:"" } })
基于MVVM模式的前端框架三巨头:Vue、React、AngularJS,国内目前非常火的就是:Vue
Vue.js介绍
Vue.js是一款以数据为驱动的前端框架。
0.Vue库下载
<!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 生产环境版本,优化了尺寸和速度 --> <script src="https://cdn.jsdelivr.net/npm/vue"></script>
1.Vue引入和绑定
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue模板语法</title> </head> <body> <!--模板语法 --> <div id="app"> <p>{{ msg }}</p> <p>{{ '张根' }}</p> <p>{{ 1+1 }}</p> <p>{{ {'name':'alex'} }}</p> <p>{{ person.name}}</p> <p>{{ 1>2?'真的':'假的'}}</p> <p>{{ person.name.split('').reverse().join('') }}</p> </div> </body> <!--1.引入vue库--> <script src="./vue.js"></script> <script> //2.实例化对象 new Vue({ el: "#app", <!--数据和视图建立绑定关系 --> data: { //数据属性 msg: "黄山", person:{"name":"wusir"} } }) </script> </html>
Vue.js模板语法
Vue的模板语法和Django的模板语法类似,通过{{ }}可帮助我们把完成处理的数据渲染成HTML标签内容。
Vue的模板语法和Django的目标语法不同的是不支持把数据,直接渲染为HTML标签的属性。否则就不需要Vue指令了。
<p>{{ msg }}</p> <p>{{ '张根' }}</p> <p>{{ 1+1 }}</p> <p>{{ {'name':'alex'} }}</p> <p>{{ person.name}}</p> <p>{{ 1>2?'真的':'假的'}}</p> <p>{{ person.name.split('').reverse().join('') }}</p> <p>{{ func1() }}</p> <!--我们还可以直接插入1个函数,但是在这个函数1要有返回结果 -->
Vue.js指令系统
Vue.js库中最重要的2个东西模板语法、指令系统,其中模板语法可以帮助我们完成数据显示,而指令系统可以帮助我们完成HTML标签属性设置、 DOM操作
指令后面例如:v-if="M"
Vue中所有指令以v-开头,当HTML和数据之间完成绑定、挂载之后, 一旦我们修改了Vue对象的数据属性时,就会被Vue对象里_data的observe立即检测到,Vue对象自动完成各种DOM操作、标签内容的更新渲染,所以相比Jquery在Vue中我们只需要关注数据属性如何更新即可。
这也就是数据(Vue.data)驱动(修改Vue.data属性的操作)视图(自动更新渲染标签内容)的设计理念。
v-text和v-html
设置标签的内容
<p v-text="msg"></p> //设置标签的内容为文本内容 <p v-html="msg"></p> //设置标签的内容为网页内容
v-if和v-show
v-if和v-show都是通过布尔值来控制标签的显示与隐藏。
v-if有更高的切换开销而v-show有更高的初始化渲染开销。因此需要非常频繁的切换使用v-show较好。如果运行是条件很少改变则使用v-if较好。
<div class="box1" v-show="isShow"></div> //css样式层面来控制显示和隐藏标签 <div class="box2" v-if="isShow"></div> //删除和添加标签来控制显示和隐藏标签
v-if和v-else
条件判断,指令可以=字符串也可以是运算结果,在{{ }}}中可插入的值,都可以设置给指令。
<div v-if="Math.random() >0.5"> Now you see me </div> <div v-else> Now you don't </div>
v-bind和v-on
v-bind指令绑定标签中所有的属性,比如img标签的src、alt以及a标签的hre。
v-bind指令也可以为标签 动态绑定class类名。
v-bind:可以简写为: 。
使用v-bind:class={ }为标签动态绑定class:
<p v-bind:class="{class1:is_ok1,class2:isok2}">Hello World!</p>
视图
<p class="class1 class2">Hello World!</p>
数据(数据库表中的数据)Model
new Vue({
el:"#app",
data(){
return {
imgId:"img1",
imgClass:"beauty",
imgSrc:"./img/timg%20(1).jpg",
imgAlt:"美女",
}
}
})
驱动(Vue的指令系统、模板语法、Django 视图函数中的render方法帮我们把数据显示到视图层)ViewMode
<img style="width:300px;height: 300px" v-bind:src="imgSrc" v-bind:alt="imgAlt" v-bind:id="imgId" v-bind:class="imgClass" >
简写形式
<img style="width:300px;height: 300px" :src="imgSrc" :alt="imgAlt" :id="imgId">
视图层(最终显示的标签)view
<img src="./img/timg%20(1).jpg" alt="美女" id="img1" class="beauty" style="width: 300px; height: 300px;">
v-on指令可以监听JavaScript中任何事件,v-on:可以简写为@
<button v-on:click="handlerChange">切换颜色</button>
简写
<button @click="handlerChange">切换颜色</button>
v-for指令
遍历字典数据,注意value和key参数的位置顺序。
<thead v-if="data.status==='ok'"> <tr> <!--遍历字典数据 --> <th v-for="(value,key) in data.title">{{ value }}</th> </tr> </thead>
遍历列表数据
<!--遍历列表数据 --> <tbody v-if="data.status==='ok'"> <tr v-for="(item,index) in data.users" :key="index"> <td>{{ index }}</td> <td>{{ item.name }}</td> <td>{{ item.gender }}</td> <td>{{ item.age }}</td> </tr> </tbody>
v-model
v-model是双向绑定,视图(View)和模型(Model)之间会互相影响,v-model的可使用于form表单中所有元素,例如input、select、textarea等。

<!DOCTYPE html> <html lang="zh-CN"> <head> <!-- 指定字符集 --> <meta charset="utf-8"> <title>学生列表</title> <style type="text/css"> td, th { text-align: center; } </style> <link href="./css/bootstrap.min.css" rel="stylesheet"> <script src="./js/jquery-2.1.0.min.js"></script> <script src="./js/bootstrap.min.js"></script> <script src="./js/vue-2.6.12.js"></script> <script src="./js/axios-0.20.0.js"></script> </head> <body> <div class="container" id="app"> <!--列表--> <div class="row"> <h3 style="text-align: center">学生列表</h3> <div class="col-lg-3"></div> <div class="col-lg-6"> <table border="1" class="table table-bordered table-hover"> <tr class="success" style="text-align: center"> <th>学号</th> <th>姓名</th> <th>生日</th> <th>地址</th> <th>操作</th> </tr> <!--注意:遍历的数据不能有空行--> <tr v-for="(student,index) in studentList" :key="index"> <td>{{student.id}}</td> <td>{{student.name}}</td> <td>{{student.birthday}}</td> <td>{{student.address}}</td> <td> <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#updateModal" @click="findById(student.id)">修改</a> <a class="btn btn-default btn-sm" href="javascript:void(0)" @click="deleteStu(student.id)">删除</a> </td> </tr> <tr> <td colspan="9" align="center"> <a class="btn btn-primary" data-toggle="modal" data-target="#addModal">添加学生</a> </td> </tr> </table> </div> <div class="col-lg-3"></div> </div> <!-- 新增学生表单: 通过v-model双向绑定--> <div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="addModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="addModalLabel">新增</h4> </div> <div class="modal-body"> <div class="form-group"> <label>学号:</label> <input type="text" class="form-control" name="id" placeholder="请输入学号" v-model="studentAdd.id"> </div> <div class="form-group"> <label>姓名:</label> <input type="text" class="form-control" name="name" placeholder="请输入姓名" v-model="studentAdd.name"> </div> <div class="form-group"> <label>生日:</label> <input type="text" class="form-control" name="birthday" placeholder="请输入生日" v-model="studentAdd.birthday"> </div> <div class="form-group"> <label>地址:</label> <input type="text" class="form-control" name="address" placeholder="请输入地址" v-model="studentAdd.address"> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" @click="save()">新增</button> </div> </div> </div> </div> <!-- 修改 --> <div class="modal fade" id="updateModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="updateModalLabel">修改</h4> </div> <div class="modal-body"> <div class="form-group"> <label>学号:</label> <input type="text" class="form-control" name="id" disabled v-model="studentUpdate.id"> </div> <div class="form-group"> <label>姓名:</label> <input type="text" class="form-control" name="name" placeholder="请输入学号" v-model="studentUpdate.name"> </div> <div class="form-group"> <label>生日:</label> <input type="text" class="form-control" name="birthday" placeholder="请输入姓名" v-model="studentUpdate.birthday"> </div> <div class="form-group"> <label>地址:</label> <input type="text" class="form-control" name="address" placeholder="请输入地址" v-model="studentUpdate.address"> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" @click="update()">修改</button> </div> </div> </div> </div> </div> </body> <script> let app = new Vue({ //视图View Dom对象 el: "#app", //数据模型 Model data: { studentList: [],//接收学生列表 studentAdd: {},//接收新增学生数据 studentUpdate: {},//接收修改学生数据 }, methods: { //查询所有学生 queryAll() { axios.get("/studentServlet?action=queryAll").then(resp => { this.studentList = resp.data; console.log(this.studentList); }) }, //新增学生 save() { axios.post("/studentServlet?action=save", this.studentAdd).then(response => { if (response.data == "OK") { //1. 清空添加对应对象 this.studentAdd = {}; //2. 关闭添加的模态框 $("#addModal").modal('hide');//bootstrap关闭模态框的固定方法 //3. 重新执行查询列表方法 this.queryAll(); } }) }, //根据id查询学生信息 findById(studentId) { axios.get("/studentServlet?action=queryById&id=" + studentId).then(response => { this.studentUpdate = response.data; console.log(this.studentUpdate); }) }, //修改学生 update() { axios.post("/studentServlet?action=update", this.studentUpdate).then(resp => { if (resp.data == "OK") { //1. 清空添加对应对象 this.studentUpdate = {}; //2. 关闭修改的模态框 $("#updateModal").modal('hide');//bootstrap关闭模态框的固定方法 //3. 重新执行查询列表方法 this.queryAll(); } }) }, //删除学生,注意不能使用delete deleteStu(studentId) { axios.get("/studentServlet?action=delete&id=" + studentId).then(resp => { if (resp.data == "OK") { //重新执行查询列表方法 this.queryAll(); } }) } }, //页面加载完毕之后,会自动调用created()方法 created() { this.queryAll(); } } ) </script> </html>
vue.derective()自定义指令
vue支持指令虽少但可帮我们完成大部分的dom操作可以完成的工作,当以上提到指令无法满足我们实际开发需求时,也可以对其进行扩展。
我们可以通过vue.derective自定义1个在所有组件均可使用的全局指令,也可以在某1个组件内自定义1个局部指令。
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用1次,指令第一次绑定到元素时调用。在这里可以进行1次性的初始化设置。
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
1.自定义全局指令

<template> <div id="app"> <h3 v-tack:left="position">我是自定义的指令</h3> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> <button @click="setPositionHandler">修改定位属性</button> </div> </template> <script> export default { name: 'app', data () { return { position: 300 } }, methods:{ setPositionHandler(){ this.position+=3 } } } </script> <style> body{ height: 2000px; } #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; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>

<template> <div id="app"> <h3 v-tack:left="position">我是自定义的指令</h3> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> <button @click="setPositionHandler">修改定位属性</button> </div> </template> <script> export default { name: 'app', data () { return { position: 300 } }, methods:{ setPositionHandler(){ this.position+=3 } } } </script> <style> body{ height: 2000px; } #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; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
2.在组件内定义局部指令

<template> <div id="app"> <h3 v-tack:left="position">我是自定义的指令</h3> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> <button @click="setPositionHandler">修改定位属性</button> </div> </template> <script> export default { name: "app", data() { return { position: 300 }; }, methods: { setPositionHandler() { this.position += 3; } }, directives: { tack: { inserted: function(el, binding) { // 指令的定义 //el为绑定元素,可以对其进行dom操作 console.log(binding); //一个对象,包含很多属性属性 }, bind: function(el, binding, vnone) { console.log("===========>", el); el.style.position = "fixed"; el.style[binding.arg] = binding.value + "px"; }, //绑定的标签修改属性时触发 update: function(el, binding, vnone) { el.style[binding.arg] = binding.value + "px"; } } } }; </script> <style> body { height: 2000px; } #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; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
4.案例
Vue实现轮播图

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>轮播图</title> <script src="./vue.js"></script> </head> <body> <div id="app"> <img style="width: 200px;height: 200px " :src="images[current_index].imgsrc" alt=""> <br> <button @click="next_one">下一张</button> <button @click="previous_one">上一张</button> </div> </body> <script> new Vue({ el: "#app", data() { return { images: [ {"id": 1, "imgsrc": "./img/1.jpg"}, {"id": 2, "imgsrc": "./img/2.jpg"}, {"id": 3, "imgsrc": "./img/3.jpg"}, ], current_index: 0 } }, methods: { previous_one() { if (this.current_index === 0) { this.current_index = this.images.length - 1 } else { this.current_index-- } }, next_one() { if (this.current_index === this.images.length - 1) { console.log(this.current_index); this.current_index = 0 } else { this.current_index++ } }, } }) </script> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>轮播图</title> <script src="./vue.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script> </head> <body> <div id="app"> <img style="width: 200px;height: 200px " :src="images[current_index].imgsrc" alt=""> <br> <button @click="next_one">下一张</button> <button @click="previous_one">上一张</button> </div> </body> <script> new Vue({ el: "#app", data() { return { images: [ {"id": 1, "imgsrc": "./img/1.jpg"}, {"id": 2, "imgsrc": "./img/2.jpg"}, {"id": 3, "imgsrc": "./img/3.jpg"}, ], current_index: 0 } }, methods: { previous_one() { if (this.current_index === 0) { this.current_index = this.images.length - 1 } else { this.current_index-- } }, next_one() { if (this.current_index === this.images.length - 1) { console.log(this.current_index); this.current_index = 0 } else { this.current_index++ } }, }, //组件创建完成时执行的钩子函数 created() { //自动轮播图使用箭头函数(=>)执行Vue对象. setInterval(() => { if (this.current_index === this.images.length - 1) { console.log(this.current_index); this.current_index = 0 } else { this.current_index++ } }, 2000) }, }) </script> </html>
Vue实现列表切换

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script> <script src="vue.js"></script> <style> .active1{ color: red; } </style> </head> <body> <div id="app"> <p v-bind:class="{class1:is_ok1,class2:isok2}">Hello World!</p> <div> <!--绑定click事件时传入参数(当前index) --> <span @click="click_handler(v.id)" v-for="v,i in (categoryList)" :key="v.id" :class="{active1:v.id==current_index}">{{ v.name}}</span> <ul> <li v-for="v,i in (courseList)" :key="v.id"> {{v.name}} </li> </ul> </div> </div> </body> <script> new Vue({ el: "#app", data() { return { "categoryList":[], "courseList":[], "current_index":0, "is_ok1":true, "isok2":true, } }, methods: {click_handler(index){ <!--动态修改当前index的值 --> this.current_index=index; $.ajax({ //动态更新课程列表 url:`https://api.luffycity.com/api/v1/course/actual/?category_id=${index}`, type:'get', dataType:'json', success:(result)=>{ let data =result.data; if(result.code==0){ this.courseList=data } } }) },}, created() { $.ajax({ url: "https://api.luffycity.com/api/v1/course/category/actual/?courseType=actual", type: "get", dataType: "json", success:(result)=>{ if(result.code==0){ let data=result.data; //在数组位置插入1个元素 data.unshift({ "id": 0, "name": "全部" }); this.categoryList=data } } }) }, }) </script> </html>
Vue实现音乐播放器

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>音乐播放器</title> <style> .active { background-color: deepskyblue; } </style> <script src="./vue.js"></script> </head> <body> <div id="music"> <!--动态获取歌曲 audio标签 audio结束之后自动执行ended --> <audio :src="musicData[current_index].songSrc" controls autoplay @ended="endedHandle"></audio> <ul> <li v-for="v,i in (musicData)" :key="v.id" @click="clickHandle(v.id)" :class="{active:v.id==current_index}"> <h2>歌手:{{ v.author }}</h2> <p>歌名:{{ v.name }}</p> </ul> </div> </body> <script> let musicData = [ {id: 1, name: "孤独不苦", author: "张卫健", songSrc: './music/1.mp3'}, {id: 2, name: "滚滚长江东逝水", author: "杨慎", songSrc: './music/2.mp3'}, {id: 3, name: "恋曲1990", author: "罗大佑", songSrc: './music/3.mp3'}, ]; new Vue({ el: "#music", data() { return { current_index: 1, musicData: [], } }, created() { //模拟后台获取数据,给model赋值。 this.musicData = musicData }, methods: { clickHandle(id) { this.current_index = id; }, endedHandle(){ //当前歌曲结束自动下一曲 this.current_index++; } } }) </script> </html>
5.计算属性和侦听器
在{{模板内}}使用表达式非常便利,但是设计模板语言的初衷是用于简单运算,在模板内放入太多逻辑会让模板过重难以维护。
所以在模板内遇到任何复杂的逻辑,我们都应当使用计算属性,而不能使用模板直接插值。
1.Vue侦听器(watch)
watch可以监听到 Vue对象中某1个属性发生改变时的事件以及要修改的value。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.js"></script> </head> <body> <div id="app"> <p>{{ name }}</p> <p>{{ age }}</p> <button @click="clickHandle">修改</button> </div> </body> <script> new Vue({ el: "#app", data() { return {name: "Martin",age:17} }, methods:{ clickHandle(){ this.name="Zhang"; this.age++; } }, <!--watch可以监听到当某1个属性发生改变时的事件以及要修改的value --> watch: { "name": function (new_name) { if(new_name=="Zhang"){ this.name=`Martin.${new_name} ` } }, "age":function (new_age){ //注意报错无限更新循环:this.age=`新${new_age} You may have an infinite update loop in watcher with expression "age"` this.age=new_age } }, }) </script> </html>
每1次属性发生更新时都会调用watch中属性对应的函数。
需要注意的是我们不能 在属性对应的函数中,再次对该属性进行更新,只能对该属性重新赋值,否则会产生infinite update loop。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> </head> <body> <div id="app"> <p>{{ name }}</p> <p>{{ age }}</p> <button @click="clickHandle">修改</button> </div> </body> <script> var totalTable=new Vue({ el: "#app", data() { return {name: "Martin",age:17} }, methods:{ clickHandle(){ this.name="Zhang"; this.age++; } }, <!--watch可以监听到当某1个属性发生改变时的事件以及要修改的value --> watch: { "name": function (new_name) { if(new_name=="Zhang"){ this.name=`Martin.${new_name} ` } }, "age":function (new_age){ //注意报错无限更新循环:this.age=`新${new_age} You may have an infinite update loop in watcher with expression "age"` this.age=new_age+1 } }, }); totalTable.age=19 </script> </html>
2.Vue计算属性(computed)
计算属性compute,可以同时自动监听到Vue对象中多个数据属的变化。
他有什么用呢?
比如现在用户需要在list页面输入很多搜素参数,我们可以实时监听用户在input的输入和select选择,最终拼接组合成1个搜素dict传给后端。
<div id="example"> {{ message.split('').reverse().join('') }} </div>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>音乐播放器</title> <style> .active { background-color: deepskyblue; } </style> <script src="./vue.js"></script> </head> <body> <div id="music"> <!--动态获取歌曲 audio标签 audio结束之后自动执行ended --> <audio :src="current_song_src" controls autoplay @ended="endedHandle"></audio> <ul> <li v-for="v,i in (musicData)" :key="v.id" @click="clickHandle(v.id)" :class="{active:v.id==current_index}"> <h2>歌手:{{ v.author }}</h2> <p>歌名:{{ v.name }}</p> </ul> </div> </body> <script> let musicData = [ {id: 0, name: "孤独不苦", author: "张卫健", songSrc: './music/1.mp3'}, {id: 1, name: "滚滚长江东逝水", author: "杨慎", songSrc: './music/2.mp3'}, {id: 2, name: "恋曲1990", author: "罗大佑", songSrc: './music/3.mp3'}, ]; new Vue({ el: "#music", data() { return { current_index: 0, musicData: [], } }, created() { //模拟后台获取数据,给model赋值。 this.musicData = musicData }, methods: { clickHandle(id) { this.current_index = id; }, endedHandle(){ //当前歌曲结束自动下一曲 this.current_index++; } }, <!--使用computed 监听 this.musicData和this.current_index变量属性--> computed:{ current_song_src:function () { return this.musicData[this.current_index].songSrc } } }) </script> </html>
Vue组件化开发
一个完整的前端页面就是由多个块组织起来的,比如说导航条、侧边栏、内容区域等。
但是这些块的用处有所不同,比如有的块反复出现在很多地方都要用到(全局组件),有的块仅在某几处用到(局部组件)。
如果把这些块划分出来由Vue组件实现,这种组件化开发模式就会增加前端代码的可复用性、灵活性 。
通常一个应用(APP)会以一棵嵌套的组件树的形式来组织
局部组件
Vue局部组件只能在局部使用,使用一定要遵循声子、挂子、用子的步骤。
1.el属性和template属性的优先级

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>局部组件的使用</title> <script src="../vue.js"></script> </head> <body> <div id="app">{{ first_name}}</div> </body> <!--在Vue实例化对象中既定义了 el属性也定义了template属性,如果template中定义了模板内容,那么template模板的优先级大于el --> <script> new Vue({ el:"#app", data(){return {first_name:"Martin",last_name:"Zhang"}}, <!--template的优先级比el高所以会显示template中的内容 --> template:`<div class="Martin"> {{ last_name}} </div>` }) </script> </html>
在Vue实例化对象中既定义了 el属性也定义了template属性,如果template中定义了模板内容,那么template模板的优先级大于el,
这一特性才为我们在根组件之上衍生子组件,打造了基础条件。
2.应用子组件
应用子组件的流程是声明子组件、挂载子组件、使用子组件,整个应用过程就像自定义了1个html标签一样简单。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>局部组件的使用</title> <script src="../vue.js"></script> </head> <body> <div id="app">{{ first_name }}</div> </body> <script> //1.声子:声明子组件,子组件可以根组件的各种属性,除了el属性。1.自定义标签 let App = { data() { return {text: "Hello Martin!"} }, template: `<div> <h2>{{ text }}</h2> </div>` }; new Vue({ el: "#app", data() { return {first_name: "Martin", last_name: "Zhang"} }, //3.用子:把挂载到components中的组件以闭合标签( <App/>)使用起来。2.使用自定义标签 template: `<div class="Martin"> <App></App> </div>`, components: { //2.挂子:把子组件挂载到父组件(把子组件名称放在components中)3.挂载自定义标签 App } }) </script> </html>
3.组件化开发
Vue中的组件就像python中模块,一经声明定义之后,就可以在任意位置使用、嵌套该组件(模块)。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>局部组件使用更改</title> <script src="../vue.js"></script> </head> <body> <div id="app"></div> </body> <script> //1.声明子组件1:注意声明子组件时一定要使用一个容器标签把内容包含起来 let Vheader = { data() { return {header: "我是header区域"} }, template: `<div> <h1>{{ header }}1</h1> <h1>{{ header }}2</h1> </div> `, methods: {}, }; //声明子组件2:注意声明子组件时一定要使用一个容器标签把内容包含起来 let Vaside = { data() { return {aside: "我是侧边栏区域"} }, template: `<div id="aside_area">{{ aside}}</div>`, methods: {}, }; let Vbody = { data() { return {body: "我是body区域",} }, //3.使用子组件1、子组件2 template: `<div id="body_area"> <Vheader/> {{ body }} <Vaside/> </div>`, //2.挂载子组件1、子组件2 components: {Vaside, Vheader}, methods: {}, }; new Vue({ el: "#app", data() { return {header: "我是header区域", aside: "我是侧边栏区域"} }, template: `<div><Vbody></Vbody></div>`, components: {Vbody}, methods: {}, }) </script> </html>
全局组件
把1个组件声明在整个Vue中就可以在全局使用,称之为全局组件。使用一定要遵循声子、用子的步骤。
Vue.component("Vbutton", {
data() {
return {name: "按钮"}
},
template: '<button>{{ name}}</button>'
});

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>全局组件使用</title> <script src="../vue.js"></script> </head> <body> <div id="app"> </div> </body> <script> //全局组件声明因为子组件声明在了整个Vue类中 Vue.component("Vbutton", { data() { return {name: "按钮"} }, template: '<button>{{ name}}</button>' }); let Vbody = { data() { return {name: '子组件'} }, //无需挂载即可使用,因为子组件声明在了整个Vue类中 template: '<div id="vbody">{{ name }}<Vbutton></Vbutton</div>', }; new Vue({ el: "#app", data() { return {name: '根组件'} }, //无需挂载即可使用 template: '<div id="root">{{ name }}<Vbody></Vbody><Vbutton></Vbutton></div>', components: {Vbody}, }) </script> </html>
<slot></slot> 内容分发内置组件
在默认情况下全局组件的内容,在父组件中是不可自定义/修改的。在组件中嵌套slot标签可以解决这一问题。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>全局组件使用</title> <script src="../vue.js"></script> </head> <body> <div id="app"> </div> </body> <script> //全局组件声明因为子组件声明在了整个Vue类中 Vue.component("Vbutton", { data() { return {name:"按钮"} }, template: '<button><slot></slot>{{ name}}</button>' }); let Vbody = { data() { return {name: '子组件'} }, //无需挂载即可使用,因为子组件声明在了整个Vue类中 template: '<div id="vbody">{{ name }}<Vbutton>子组件</Vbutton</div>', }; new Vue({ el: "#app", data() { return {name: '根组件'} }, //无需挂载即可使用 template: '<div id="root">{{ name }}<Vbody></Vbody><Vbutton>根组件</Vbutton></div>', components: {Vbody}, }) </script> </html>
父往子组件传值(绑定自定义属性)
模块化开发虽然通过解耦的方式,增加了代码的灵活性,但是各个组件之间如何传输数据的问题就会显现出来。
如果父组件通过ajax向后端获取到了数据,如果和把数据传递给子组件呢?
1.子组件声明props属性用以接收值
2.父组件可以通过template给子组件绑定标签属性(v-bind=‘{id:1,class:'box '}’)或者绑定自定义属性(v-bind:post_content='父组件中的数据属性')给自组件传值。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>父往子组件传值</title> <script src="../vue.js"></script> </head> <body> <div id="app"> </div> </body> <script> let Vbody = { template: `<div id="Vbody"> 父组件数据: {{ parent_msg}} {{ post_content.age }} {{ post_content.job_title }} </div>`, //1.子组件声明props属性用以接收值 props: ['parent_msg', 'post_content'], methods: {}, }; new Vue({ el: "#app", data() { return { name: "Martin", post: {id: 1, title: "My Journey with Vue"}, post_content: {age: 18, job_title: "SoftwareEngineer"}, } }, //2.父组件通过template给子组件v-bind=绑定值 template: `<div id="Vbody"><Vbody :parent_msg='name' v-bind='post' v-bind:post_content='post_content'></Vbody></div>`, components: {Vbody}, methods: {}, }) </script> </html>
子往父组件传值(绑定自定义事件)
我们可以从根组件把数据输出到各个子组件那么,数据在子组件被处理修改之后,如何回到根组件呢?
在子组件通过 $emit("在父组件中声明自定义事件","传值")修改父组件的值。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>子组件往父组件传值</title> <script src="../vue.js"></script> </head> <body> <div id="app"> </div> </body> <script> let grand_children_component = { template: `<div> <p>{{ first_name }}</p> <button id="grand_children_component" @click='clickHandle'>点击</button> </div> `, methods: { clickHandle() { //在子组件通过 $emit("在父组件中声明自定义事件","传值")调用 this.$emit("myClickHandle", this.first_name) } }, props: ['first_name'], created() { console.log(`我是Vue的子组件的自组件:${this._uid}`) } }; let children_component = { //在父组件中声明自定义事件 template: `<div id="children_component"><grand_children_component :first_name='first_name' @myClickHandle='myClickHandle' /></div>`, components: {grand_children_component}, props: ['first_name'], created() { console.log(`我是Vue的子组件:${this._uid}`) }, methods: { //在子组件通过 $emit("在父组件中声明自定义事件","传值")调用 myClickHandle(first_name) { this.$emit("myClickHandle1", this.first_name) } } }; new Vue({ el: "#app", data() { return {name: "Martin"} }, //在父组件中声明自定义事件 template: ` <div id="root"><children_component :first_name='name' @myClickHandle1='myClickHandle1' /> </div>`, components: {children_component}, created() { console.log(`我是Vue:${this._uid}`) }, methods: { //在子组件通过 $emit("在父组件中声明自定义事件","传值")调用 myClickHandle1(first_name) { console.log(first_name); this.name += first_name + '1' } } }) </script> </html>
平行组件传值
以上2种组件之间传值的方式都需要在2个组件间一级一级地上传/下达数据,数据传输效率低下,如果2个需要交互数据的组件之间有个中间信息枢纽(队列/bus)直接把双方连接起就好了。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>公交车传值</title> <script src="../vue.js"></script> </head> <body> <div id="app"> <child/> </div> </body> <script> //0.声明bus 数据枢纽 let bus = new Vue(); let grand_child = { template: `<div id="grand_child"><button @click='clickHandler'>传递</button></div>`, data() { return {msg: "我是来自grand_child组件的数据"} }, methods: { clickHandler() { //1.发送数据 bus.$emit('testdata', this.msg) } } }; let child = { data() { return {DataFromGrandChild: ''} }, //3.使用数据 template: `<div id="child">{{ DataFromGrandChild }}<grand_child/></div>`, components: {grand_child}, created() { //2.接收数据 bus.$on('testdata', (value) => { this.DataFromGrandChild = value; }) } }; new Vue({ el: "#app", data() { return {name: "Martin"} }, components: {child}, }) </script> </html>
filter数据过滤器
filter可以过滤和处理后端传送过来的数据,就行小菜一样给事物添油加醋。
filter分为全局过滤器和局部过滤器,其中全局过滤器可以在任何组件中使用而局部filter仅可以在当前组件中使用。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>局部过滤器的使用</title> <script src="vue.js"></script> <script src="moment.js"></script> </head> <body> <div id="app"> </div> </body> <script> //全局过滤器:可以在所有组件中使用 Vue.filter("DtaeFromat", function (val) { return moment(val).format("YYYY-MM-DD") }); let son = { data() { return {date: "1937-07-07", publish: "新华日报社"} }, template: `<div> <h1>{{ publish|MyReverse }}:{{ date| DtaeFromat}}</h1> <p>1937年7月7日,抗日战争全面爆发,11月20日,国民政府正式迁都重庆市</p> <p>{{date|DataFronNow }} </p> </div>`, //局部过滤器:紧急在当前组件使用 filters: { MyReverse: function (value) { console.log(value); return value.split('').reverse().join('') }, DataFronNow: function (value) { return moment(value).fromNow() } } }; new Vue({ el: "#app", data() { return {} }, template: `<div><son/></div> `, components: {son}, }) </script> </html>
Vue.js生命周期的钩子函数
我们每次切换刷新页面时该页面对应的组件在默认情况下都会被创建和销毁1次,我们称之为Vue实例的1次生命周期。
1个Vue实例从创建到销毁一共需要经历以下几个阶段,Vue的开发者在这些阶段预留了钩子函数,以便我们做一些操作。
组件创建前(beforeCreate):在这个阶段你可以看到组件对象,但是组件的数据属性还没有完成挂载。
组件创建后(created):在这个阶段组件创建完成template属性已经挂载到组件,但是还没有完成DOM的渲染。我们可以发送ajax更新组件的数据属性。
组件的template数据属性装载数据到DOM之前(beforeMount):在这个阶段组件还没人渲染出现在DOM标签中。
组件的template数据属性装载数据到DOM之后(mounted):在这个阶段组件中定义的template已经渲染到DOM标签中。所以可以在这个阶段做一些DOM操作。
当组件和组件内定义的DOC完成加载后(beforeUpdate):用户更通过操作数据属性更新DOC 之前
当组件和组件内定义的DOC完成加载后 (beforeUpdate):用户更通过操作数据属性更新DOC 之后
组件销毁前(beforeDestroy):用户通过v-if删除组件前
组件销毁后(destroyed)用户通过v-if删除组件后,注意:当组件销毁时 一定要销毁组件创建的定时器等附带数据。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue钩子函数</title> <script src="vue.js"></script> </head> <body> <div id="app"> <son></son> </div> </body> <script> let grandSon = { data() { return {msg: "hello world", number: null, timer: null} }, template: `<div id="grandSon">我是grandSon组件{{ msg }} <button @click="changeHandle">更新数据</button> <p>{{ number }}</p> </div>`, methods: { changeHandle() { this.msg = "哈哈哈" } }, beforeCreate() { //组件对象有 console.log('组件创建之前', this); //但是组件的属性没有挂载完毕 console.log(this.msg); }, created() { //组件和数据数据已经完成挂载 //在created钩子函数可以发送ajax向后端请求数据 console.log('组件创建之后', this); this.timer = setInterval(() => { this.number++ }, 1000); this.msg = "嘿嘿嘿" }, beforeMount() { console.log("数据到装载到DOM之前", document.getElementById('app')); }, mounted() { console.log('数据到装载到DOM后', document.getElementById('app')); }, beforeUpdate() { // 在DOM修改之前调用此钩子,应用:获取原始的DOM console.log(document.getElementById('app').innerHTML); }, updated() { // 在DOM修改之后调用此钩子,应用:获取最新的DOM console.log(document.getElementById('app').innerHTML); }, beforeDestroy() { console.log('删除组件前'); }, destroyed() { //注意:组件销毁时 销毁组件创建的定时器 clearInterval(this.timer); console.log('删除组件后'); }, activated() { console.log('组件被激活了'); }, deactivated() { console.log('组件被停用了'); } }; let son = { data() { return {isok: true} }, template: `<div id="son"> <keep-alive> <grandSon v-if="isok"> </grandSon> </keep-alive> <button @click="destroyedHandle">删除grandSon组件</button> </div>`, components: {grandSon}, methods: { destroyedHandle() { this.isok = !this.isok } } }; new Vue({ el: "#app", data() { return {} }, components: {son}, }) </script> </html>
如果每1次刷新切换页面时都要创建、axios从后端获取数据、渲染、销毁当前页面对应组件,这样无疑会消耗很大性能。
或者在某些场景下我们需要保存原访问页面的状态。
这时我们可以通过<keep-alive>需要暂存的组件</keep-alive>内置组件,对组件进行暂存和激活。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="./node_modules/vue/dist/vue.min.js"></script> <script src="./node_modules/vue-router/dist/vue-router.min.js"></script> <title>Document</title> </head> <body> <div id="app"></div> </body> <script> let App = { name: "App", template: `<div> <router-link to="/home">首页</router-link> <router-link to="/courses">课程列表</router-link> <keep-alive> <router-view></router-view> </keep-alive> </div>` }; let Home = { name: "Home", template: `<div>我是首页</div>`, created() { console.log("首页组件创建了") }, mounted() { console.log("首页组件Dom加载了") }, destroyed() { console.log("首页组件销毁了") } }; let CourseList = { name: "CourseList", template: `<div id='courseList'><h3 @click="clickHandler">我是课程组件</h3></div>`, methods: { clickHandler(e) { e.target.style.color = "red" } }, created() { console.log("课程组件创建了") }, mounted() { console.log("课程组件Dom加载了") }, destroyed() { console.log("课程组件销毁了") } }; let router = new VueRouter({ routes: [ { path: "/", redirect: "/home" }, { path: "/home", component: Home }, { path: "/courses", component: CourseList }, ] }) new Vue({ el: "#app", router, data() { return { msg: "前端" } }, template: `<App/>`, components: { App } }) </script> </html>
Vue-router单页应用开发
单页应用的全称single-page application,简称 SPA,它可以在保证前端页面不刷新的前提下,在1个页面内完成路由跳转与用户进行交互。
单页面应用提升了用户体验、避免了反复刷新整个页面或跳转到其他新的页面,增加页面资源加载消耗的问题。
使用Vue.js 我们已经完成了组件开发,那么这些不同的组件之间如何在1个页面(App组件)来回跳转,完成用户交互呢?
结合Vue Router + Vue.js我们可将组件 (components) 映射到路由系统 (routes)中,然后告诉 Vue Router在哪里渲染它们。
它就像Django的路由系统一样,通过记录url和视图函数的映射关系,连接起了视图和视图函数。
Vue-router的实现原理
Vue-router就是在你访问某1个url时,定量加载当前页面所需的资源,而非全量加载避免了资源加载过多导致白屏现象。
其实我们自己使用原生的JavaScript也可以完成1个Vue-router。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div> <a href="#/login/">登录页面</a> <a href="#/registe">组册页面</a> <div id="component"></div> </div> </body> <script> //当用户跳转路由时都会触发Windows的onhashchange事件 window.onhashchange = function () { //使用location可以获取到用户当前访问的URL let currentAccess = location.hash let currentComponent = document.getElementById("component") switch (currentAccess) { case '#/login/': currentComponent.innerHTML = "我是登录页面" break case '#/registe': currentComponent.innerHTML = "我是组册页面" break default: currentComponent.innerHTML = "404页面" } } </script> </html>
Vue-router.js提供的内置信息
router-link:渲染a标签的herf属性对应url地址
router-view:路由出口渲染的是herf跳转地址对应的内容
this.$route:获取路由信息 params/query
this.$router:路由对象
Vue-router的基本使用
router-link和router-view是vue-router.js库提供的2个全局组件。
router-link:默认会被渲染成a标签to属性=href属性。需要注意的是router-link:to="想要的是路由信息中配置的路由信息中name,一定不是components在这种配置的name"
router-view:是所有router-link对应的路由出口,就是对应URL地址完匹配后跳转到对应的组件的内容将会渲染在router-view中。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>vue-router的基本使用</title> <script src="vue.js"></script> <!--vue-router基于vue,所以在导入vue-router之前一定要导入vue --> <script src="vue-router.js"></script> </head> <body> <div id="app"></div> </body> <script> //如果是以后模块化编程,vue.proptotype.$vueRouter=VueRouter //Vue.use(VueRouter) let Home = { data() { return {} }, template: `<div>我是首页</div>`, }; let Course = { data() { return {} }, template: `<div>我是免费课程</div>`, }; //1.创建路由 const router = new VueRouter({ //2.定义路由规则:url地址对应的组件,当用户访问当前url时,渲染url对应的组件。 //mode:"history", routes: [ { path: "/", redirect: '/home',//设置跳转 }, { path: "/home", component: Home, }, { path: '/course', component: Course, }, ] }); let App = { data() { return {} }, //router-link和router-view是vue-router.js库提供的2个全局组件。 //4.router-link默认会被渲染成a标签to属性=href属性。 //5.router-view是所有router-link对应的路由出口。 //6.当前路由匹配到的组件将会被渲染在router-view中。 template: `<div> <div class="header"> <router-link to="/home">首页</router-link> <router-link to="/course">免费课程</router-link> </div> <router-view></router-view> </div>`, }; new Vue({ el: "#app", //3.挂载路由实例 router, data() { return {} }, template: `<App/>`, components: { App } }) </script> </html>
命名路由
Vue和Django一样支持给路由命名,给路由命名可以让我们更加灵活的渲染路由。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>命名路由</title> <script src="vue.js"></script> <script src="vue-router.js"></script> </head> <body> <div id="app"> </div> </body> <script> let Home = { template: `<div>我是首页</div>`, }; let Course = { template: `<div>我是课程页</div>`, }; const router = new VueRouter({ routes: [ {path: '/',redirect: '/home'}, //命名路由 {path: '/home',name:"Home", component: Home}, {path: '/course',name:'Course', component: Course} ] }); let App = { template: `<div> <div class="header"> <router-link :to='{name:"Home"}'>首页 </router-link> <router-link :to='{name:"Course"}'>免费课程</router-link> </div> <router-view></router-view> </div>`, }; new Vue({ el: '#app', router, data() { return {} }, template: `<App/>`, components: {App}, }) </script> </html>
动态路由
query携带url参数,params动态路由。
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
我们经常需要把某种模式匹配到的所有路由,全都映射到同一组件。
例如,我们有一个 User
组件,对于所有 ID 各不相同的用户,都要使用同一个组件来渲染。
那么,我们可以在 vue-router
的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>动态路由匹配</title> <script src="vue.js"></script> <script src="vue-router.js"></script> </head> <body> <div id="app"> </div> </body> <script> let User = { data() { return {user_id: null} }, template: `<div>我是用户{{ user_id }}</div>`, //在路由匹配到的组件获取当前url的params和query watch: { '$route'(to, from) { console.log(to.params.id); // 发送ajax this.user_id = to.params.id; console.log(from) } } }; let router = new VueRouter({ routes: [ //匹配动态路由:/user/1 或者/user/2 {path: '/user/:id', name: 'User', component: User}, ] }); //动态路由:/user/1 或者/user/2 let App = { template: `<div> <div class="header"> <router-link :to="{name:'User',params:{id:1}}">用户1</router-link> <router-link :to="{name:'User',params:{id:2}}">用户2</router-link> </div> <router-view></router-view> </div>` }; new Vue({ el: "#app", router, data() { return {} }, template: `<App/>`, components: {App}, }) </script> </html>
声明式路由 versus 编程式路由导航
单页面应用一般都是都是基于组件在前端完成路由跳转的。那么开发人员导航用户在不同Vue组件之间切换呢? 路由!
有2种方式:
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
1.我们可以通过内置组件<router-link></router-link>渲染出a标签当用户点击时完成路由刷新。(声明式导航)
2.也可以通过绑定事件的形式,触发JavaScript代码(this.$router.push({name: "Home"})) 完成路由跳转。(编程式导航)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>编程式导航</title> <script src="vue.js"></script> <script src="vue-router.js"></script> </head> <body> <div id="app"> </div> </body> <script> let Home = { template: `<div>我是首页</div>` }; //4.创建组件:显示用户路由后看到的内容 let User = { data() { return {user_id: null} }, template: `<div>我是用户{{ user_id }} <button @click='clickHandler'>跳转到首页</button> </div> `, methods: { //编程式导航 clickHandler() { this.$router.push({ name: "Home" }) } }, watch: { '$route'(to, from) { this.user_id = to.params.id; } } }; //3.创建路由系统:当用户点击 标签时把用户引导到组件 let router = new VueRouter({ routes: [ {path: "/home", name: "Home", component: Home}, {path: "/user/:id", name: "User", component: User}, ] }); //2.定义路由入口(前端) let App = { template: ` <div> <div class="header"> <router-link :to='{name:"User",params:{id:1}}'>用户1</router-link> <router-link :to='{name:"User",params:{id:2}}'>用户2</router-link> </div> <router-view></router-view> </div> `, }; //1.启动路由系统 new Vue({ el: '#app', router, data() { return {} }, template: `<App/>`, components: {App} }) </script> </html>
嵌套路由
在实际生活中的应用界面,通常由多层嵌套的组件组合而成。同理URL也可以根据组件的嵌套关系进行嵌套,就像Django urls中的includ()功能。
/user/zhanggen/profile /user/zhanggen/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+

import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import Course from '@/components/Course' import User from '@/components/User/user' import UserProfile from '@/components/User/user_profile' import UserPosts from '@/components/User/user_post' Vue.use(Router) export default new Router({ mode: "history", routes: [ { path: '/', redirect: '/home', }, { path: '/home', name: 'Home', component: Home }, { path: '/course', name: 'Course', component: Course }, { path:'/user/:id', name:"User", component:User, children:[ // 当 /user/:id/profile 匹配成功, UserProfile 会被渲染在 User 的 <router-view> 中 {path:'profile', component: UserProfile,name:"profile"}, // 当 /user/:id/profile 匹配成功,UserPosts 会被渲染在 User 的 <router-view> 中 {path:'posts', component: UserPosts,name:"posts"}, ] } ] })

<template> <div> <div class="user"> <h2>欢迎来到{{ $route.params.id }}的首页</h2> <router-link :to='{name:"profile"}'>头像</router-link> <router-link :to='{name:"posts"}'>首页</router-link> <router-view></router-view> </div> </div> </template> <script> export default { data(){ } } </script> <style> </style>

<template> <div> <h2>{{ $route.params.id }}的文章</h2> </div> </template> <script> export default { } </script> <style> </style>

<template> <div> <h2>{{ $route.params.id }}的头像</h2> </div> </template> <script> export default { } </script> <style> </style>
子路由和url动态传参

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="./node_modules/vue/dist/vue.min.js"></script> <script src="./node_modules/vue-router/dist/vue-router.min.js"></script> <title>Document</title> </head> <body> <div id="app"></div> </body> <script> const BackednData = { frontend: { courseName: "Vue从放弃到到入门" }, backend: { courseName: "Django从入门到放弃" } }; let App = { name: "App", template: `<div> <router-link to="/home">首页</router-link> <router-link to="/courses">课程列表</router-link> <router-view></router-view> </div>` }; let Home = { name: "Home", template: `<div>我是首页</div>` }; let Course = { name: "Course", template: `<div> <p>{{tableData.courseName}}</p> </div>`, data() { return { UrlParam: '', tableData: '', } }, created() { //1.发axios初始化课程信息 this.UrlParam = "frontend" this.tableData = BackednData[this.UrlParam] }, //使用watch在当前组件内部监控$route进而监控到路由信息的变化 watch: { $route(to, from) { console.log(`我从${from.path}来到${to.path}去`) //再根据路由参数发送axios/直接取值获取数据,在同1个组件中切换课程信息 this.UrlParam = to.params.courseName this.tableData = BackednData[this.UrlParam] }, } }; let CourseList = { name: "CourseList", //注意:router-link:to="想要的是路由信息中配置的name,一定不是components在这种配置的name" template: `<div id='courseList'> <router-link :to="{name:'course666',params:{courseName:'frontend'}}">前端相关课程</router-link> <router-link :to="{name:'course666',params:{courseName:'backend'}}">后端相关课程</router-link> <router-view></router-view> </div>` }; let router = new VueRouter({ routes: [ { path: "/", redirect: "/home" }, { path: "/home", component: Home }, { path: "/courses", component: CourseList, children: [ { name: "course666", //动态路由以 :开头 path: "/courses/:courseName", component: Course } ] }, ] }) new Vue({ el: "#app", router, data() { return { msg: "前端" } }, template: `<App/>`, components: { App } }) </script> </html>
路由的元信息(meta)
配置路由时我们还可以配置路由的meta信息。
我们可以根据当前路由携带的meta信息,在全局路由守卫(我把它看做Django中的中间件)阻断/放行用户到组件间的访问,做权限控制功能。
全局路由守卫(beforeEach())
有时候感觉Vue和Django中的功能还是挺像的,Django中有中间件可以全局监控所有请求和响应。而Vue中有router.beforeEach。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="./node_modules/vue/dist/vue.min.js"></script> <script src="./node_modules/vue-router/dist/vue-router.min.js"></script> <title>Document</title> </head> <body> <div id="app"> </div> </body> <script> Vue.use(VueRouter); let App = { name: "App", template: `<div> <router-link to="/login">登录</router-link> <router-link to="/home">首页</router-link> <router-link to="/blog">博客</router-link> <button @click='handleExit'>退出</button> <router-view></router-view> </div>`, methods:{ handleExit(){ localStorage.removeItem('user') } } }; let Login = { name: "Login", template: ` <div> <input type="text" v-model="user"> <input type="password" v-model="password"> <input type="button" value="登录" @click="loginHandler"> </div> `, data() { return { user: '', password: '' } }, methods: { loginHandler() { // 用户点击登录是暂时把用户信息存放在localstrage中 localStorage.setItem("user", { user: this.user, password: this.password }) console.log(this.$route) // 跳转到博客页面 this.$router.push({ name: "blogpath" }) } } }; let Home = { name: "Home", template: `<div>我是首页</div>`, }; let Blogs = { name: "Blogs", template: `<div id='courseList'>我是博客组件</div>`, methods: {} }; let router = new VueRouter({ routes: [ { path: "/", redirect: "/home" }, { path: "/login", component: Login }, { path: "/home", component: Home }, { path: "/blog", name: "blogpath", component: Blogs, //给未来的路由做权限控制 meta: { //证明用户访问该组件时需要登录 needAuth: true } }, ] }); //路由全局守卫:也就是监控所有路由的变化 router.beforeEach((to, from, next) => { //查看当前访问的URL是否需要认证 if (to.meta.needAuth === true) { //需要认证就查看是否已认证 let currentUser = localStorage.getItem('user') if (currentUser) { //已经认证直接放行,注意如果不调用next页面会卡住 next() } else { //未认证去认证 next({ 'path': '/login' }) } } else { next() } }); new Vue({ el: "#app", router, data() { return { msg: "前端" } }, template: `<App/>`, components: { App } }) </script> </html>
获取原生DOM的方式
给标签或者vue组件添加ref属性可以方便我们获取到它们的 DOM对象。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>refs属性的使用</title> <script src="vue.js"></script> </head> <body> <div id="app"></div> </body> <script> Vue.component('Test', { data() { return {} }, template: `<div>我是测试组件</div>` }); let App = { data() { return {} }, //给标签或者vue组件添加ref属性可以,方便我们获取到它们的DOM对象 template: ` <div> <input type="text" ref='input'> <Test ref='Test' /> </div> `, mounted() { //获取原生DOM对象 this.$refs.input.focus(); console.log(this.$refs.input); console.log(this.$refs.Test); //获取父级对象 console.log(this.$refs.Test.$parent); //获取挂载的根组件 console.log(this.$refs.Test.$root); //获取子级标签 console.log(this.$refs.Test.$children); } }; new Vue({ el: "#app", data() { return {} }, template: `<App/>`, components: {App} }) </script> </html>
传统的web开发模式
1.传统的web开发模式面临的困境
我1个人在传统前端开发模式下滞后了很久,久到自以为全栈。我使用Django的模板语言,遇到项目里需要的UI插件我就自己去官网下载文件,然后在html文件中<script>、<link>、DOM操作。
但是......随着这些第三方依赖库越多,JavaScript开辟的作用域就越多, 调试时出现页面奔溃的概率也越来越高,导致整个web项目无法扩展。
因为你不知道别人写的这些依赖库之间的依赖的版本、依赖顺序是什么样的?
你只能自己去不断改变JavaScript的引用顺序、 不断尝试。
总结下来传统的前端开发模式会面临以下几种问题:
手动下载第三方依赖包
html、css、js文件依赖关系错综复杂
静态资源请求效率低
对模块化支持不友好
浏览器对高级JavaScript(ES6)的兼容
2.突破传统的web开发模式
2.1.npm解决第三方依赖包下载和版本管理,解决我手动下载和管理第三方依赖包的问题。
npm 是node.js中包管理器(pip),所以想要使用npm需要依赖于node.js开发环境。
之前我们使用jQuery需要去jQuery的官网下载jQuery.js、使用bootstrap去bootstrab官网下载bootstrap......................
当脚手架已经搭建成功之后,此时我们的项目已经完全支持了模块化开发。
只需要npm install包名 下载第三方包,然后var webpack = require('包名')导入包名使用即可。
npm install module_name -S 即 npm install module_name --save 写入dependencies 线上环境
npm install module_name -D 即 npm install module_name --save-dev 写入devDependencies 开发环境
npm install module_name -g 全局安装(命令行使用)
npm install module_name 本地安装(将安装包放在 ./node_modules 下)
2.2:前端文件支持模块化开发:实现了每个css、JavaScript、Vue文件均可被当做1个模块进行导入,提升前端代码重用性、可扩展性、易维护性。
commonJS规范(CMD):借助node.js环境 module.exports.x导出然后var time=require('./time.js')导入;
导出

var person={ name:"艾青" }; var arry1=['艾青','陈万松','王海涛']; let add=function (a,b) { return a+b }; //node.js的语法:输出person对象 module.exports.person=person; module.exports.arr1=arry1; module.exports.addfunction=add;
导入

//node.js的语法导入 var time=require('./time.js'); console.log(time.person); console.log(time.arr1); console.log(time.addfunction(1,1)); //CMD comment js define
node环境运行
D:\Vue学习\day04\nodejs模块>node index.js { name: '艾青' } [ '艾青', '陈万松', '王海涛' ] 2
ES6的module:中的export default 和 import的语法,也支持模块化开发。但是无法直接在浏览器运行,需要借助webpack处理js在浏览器中的兼容性问题。
把下面2个JavaScript文件打包,module.js

var p={ name:"张三" }; export default p;
依赖main.js文件

import person from './module.js'
console.log(person.name);
alert(1);
输出

/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__module_js__ = __webpack_require__(1); console.log(__WEBPACK_IMPORTED_MODULE_0__module_js__["a" /* default */].name); alert(1); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; var p={ name:"张三" }; /* harmony default export */ __webpack_exports__["a"] = (p); /***/ }) /******/ ]);
webpack运行
D:\trap_server\Vue学习\day04\es6 module规范>webpack ./main.js ./boundle.js Hash: eec78873e020f0c1ef84 Version: webpack 3.12.0 Time: 68ms Asset Size Chunks Chunk Names boundle.js 2.98 kB 0 [emitted] main [0] ./main.js 74 bytes {0} [built] [1] ./module.js 49 bytes {0} [built]
在浏览器运行

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script src="boundle.js"></script> </html>
2.3:webpack打包器:更轻松的完成前端项目依赖包和开发代码的自动压缩、集成打包。
本质上,webpack 是一个基于node.js开发的(运行webpack需要node.js开发环境), 现代JavaScript 应用程序的静态模块打包器(module bundler)。
当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个
可以直接在浏览器运行的bundle文件。
3.DIY 1个脚手架
以上我们为突破传统前端开发模块做了很多技术调研,下面我们利用这些技术node.js+npm依赖包管理器+ES6 module新语法+webpack支持了模块化开发 实现1个脚手架。解决传统开发模式面临的各种问题。
生成package.json配置文件

{ "name": "03-module_study_further", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
在项目中局部安装webpack
cnpm i webpack@3.12.0 -D
在package.json中配置入口文件(./main.js)和出口文件(./bundle.js)
{ "name": "03-module_study_further", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "dev": "webpack ./main.js ./bundle.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^3.12.0" } }
添加webpack.config.js配置
npm run dev 先执行 package.json中的"webpack ./main.js ./bundle.js" 然后再调用webpack.config.js的配置。

{ "name": "03-module_study_further", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "dev": "webpack --config ./webpack.dev.config.js", "build": "webpack --config ./webpack.product.config.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^3.12.0" } }
线下配置

module.exports = { entry: {'main': './main.js'}, output: {'filename': './bundle.js'}, watch:true, //自定检查代码是否更新 };
线上环境配置

module.exports = { entry: {'main': './main.js'}, output: {'filename': './bundle.js'}, };
配置线上和线下环境
开发环境下执行 nmp run dev之后调用package.json之后执行 ./webpack.dev.config.js配置文件。
线上环境执行 nmp run buld之后调用调用package.json之后执行./webpack.product.config.js配置文件。
下载css-loader支持css文件模块化导入(loader)
使用npm下载第三方的库一定要注意版本!!!
cnpm install css-loader@1.0.0.0 style-loader@0.23.1 --save-dev
配置导入css模块

module.exports = { entry: {'main': './main.js'}, output: {'filename': './bundle.js'}, watch: true, module: { rules: [ {test: /\.css$/, use: ['style-loader', 'css-loader']}, ] } };
webpack中配置插件(plugin)
下载html-webpack-plugin插件
cnpm i html-webpack-plugin@3.2.0 -D
配置html-webpack-plugin插件

//导入nodejs的path模板,用于指导路径。 const path = require('path'); //使用npm下载的插件都可以在node中使用require导入 const HtmlWebpackPlugin=require('html-webpack-plugin'); module.exports = { entry: {'main': './src/main.js'}, output: { 'path': path.resolve('./dist'),//相对路径转绝对路径 'filename': 'bundle.js' }, watch: true, module: { rules: [ {test: /\.css$/, use: ['style-loader', 'css-loader']}, ] }, //添加插件 plugins: [ new HtmlWebpackPlugin({ //插件执行运行元素索引参照物 template: './src/index.html' }) ] };
支持根据参照html自动在dist目录下生成、引入打包好的js文件
D:\trap_server\Vue学习\day04\03-module_study_further>npm run dev > 03-module_study_further@1.0.0 dev D:\trap_server\Vue学习\day04\03-module_study_further > webpack --config ./webpack.dev.config.js Webpack is watching the files… Hash: 55ca4258a25c147f1848 Version: webpack 3.12.0 Time: 974ms Asset Size Chunks Chunk Names bundle.js 390 kB 0 [emitted] [big] main index.html 205 bytes [emitted] [0] (webpack)/buildin/global.js 509 bytes {0} [built] [1] ./src/main.js 198 bytes {0} [built] [2] ./src/vue.js 354 kB {0} [built] [6] ./src/App.js 74 bytes {0} [built] [7] ./src/test.css 1.14 kB {0} [built] [8] ./node_modules/_css-loader@1.0.0@css-loader!./src/test.css 215 bytes {0} [built] + 6 hidden modules Child html-webpack-plugin for "index.html": 1 asset [0] ./node_modules/_html-webpack-plugin@3.2.0@html-webpack-plugin/lib/loader.js!./src/index.html 381 bytes {0} [built] [2] (webpack)/buildin/global.js 509 bytes {0} [built] [3] (webpack)/buildin/module.js 517 bytes {0} [built] + 1 hidden module
支持导入vue文件
下载vue-loader
cnpm install vue-loader@14.1.1 vue-template-compiler@2.5.17 -D
配置

//导入nodejs的path模板,用于指导路径。 const path = require('path'); //使用npm下载的插件都可以在node中使用require导入 const HtmlWebpackPlugin=require('html-webpack-plugin'); module.exports = { entry: {'main': './src/main.js'}, output: { 'path': path.resolve('./dist'),//相对路径转绝对路径 'filename': 'bundle.js' }, watch: true, module: { rules: [ {test: /\.css$/, use: ['style-loader', 'css-loader']}, {test: /\.vue$/, use: ['vue-loader']}, ] }, //添加插件 plugins: [ new HtmlWebpackPlugin({ //插件执行运行元素索引参照物 template: './src/index.html' }) ] };
支持项目自动运行在8080端口
cnpm install webpack-dev-server@2 -D
配置package执行npm run dev时执行
"dev": "webpack-dev-server --open --hot --inline --config ./webpack.dev.config.js",

{ "name": "03-module_study_further", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "dev": "webpack-dev-server --open --hot --inline --config ./webpack.dev.config.js", "build": "webpack --config ./webpack.product.config.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "css-loader": "^1.0.0", "html-webpack-plugin": "^3.2.0", "style-loader": "^1.2.1", "vue-loader": "^14.1.1", "vue-template-compiler": "^2.5.17", "webpack": "^3.12.0", "webpack-dev-server": "^2.11.5" }, "dependencies": { "vue": "^2.5.17", "vue-router": "^3.0.2" } }
4.nodejs、npm、webpack 之间关系?
-
webpack是npm生态中的一个模块,我们可以通过npm全局安装webpack来使用webpack对项目进行打包;
-
webpack的运行依赖于node的环境,没有node是不能打包的,但是webpack打包后的项目本身只是前端静态资源和后台没有关系,也就是说不依赖与node,只要有后台能力的都可以部署项目
-
npm是于Node社区中产生的,是nodejs的官方包管理工具,当你下载安装好node的时候,npm cli就自动安装好了
-
正是因为npm的包管理,使得项目可以模块化的开发,而模块化的开发带来的这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就是webpack工具存在的意义
vue-cli脚手架
以上我通过安装webpack然后npm下载一些loader、plugin、webpack-dev-server进行集中配置实现了1个简单的脚手架,完成了前端模块化开发、第三方依赖包管理、文件的压缩打包、自动化测试,让我对脚手架的功能有了更加深入的理解。解决了传统开发模式下的面临的一些痛点。
如果每次创建1个vue项目都需要经过这么多步骤先创建1个脚手架,这无疑会大大增加前端开发人员的开发人员的工作量。如果有个1键启动脚手架的命令就好了!
vue-cli就是1个基于webpack打包器工具,只需要我在shell/cmd中执行1条非常简单的命令即可快快速生成1个支持第三方依赖包管理、前端代码打包、压缩、proxy(跨越)设置的vue项目。
我们站在vue-cli之上就可以把工作重心放在JavaScript、css的代码的实现上了,所以我们称vue-cli为vue的脚手架。
vue-cli把前端开发人员的工作重心直接带到了功能实现上,提高了开发效率和vue项目的可维护性。
使用vue-cli创建vue项目流程
1.在window/linux安装nodejs开发环境之后得到npm 包管理器。 2.使用npm下载webpack脚手架 npm install -g @vue/cli,默认安装的是vue-cli 3.x的版本。 由于使用vue-cli3创建的vue项目,隐藏webpack配置到包文件中。导致我们无法配置webpack,所有需要让当前vue-cli3版本支持创建vue-cli2模板。 3.支拉取vue-cli 2.x模板 npm install -g @vue/cli-init 4.基于vue-cli 2.x模板创建Vue项目:vue init myproject 5.确保进入创建的项目 cd myproject之后执行 npm intsall下载依赖包。 6.npm run dev 调用package.json之后进而调用config.js文件,运行整个项目。
创建vue-cli 2.x模板的vue项目
vue-cli中webpack-simple模板创建vue项目
webpack-simple模板是vue-cli中比较简单的模板。
vue init webpack-simple projectname
cd my_first_vue_project
cnpm install
npm run dev
Project is running at http://localhost:8080/
webpack output is served from /dist/
404s will fallback to /index.html
{ parser: "babylon" } is deprecated; we now treat it as { parser: "babel" }.
vue-cli中webpack模板的使用
webpack是vue-cli中比较贴近于真实开发的模板。
vue init webpack my_second ? Project name my_second ? Project description A Vue.js project ? Author 张根 <zhanggen@sensorsdata.cn> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? No ? Set up unit tests No ? Setup e2e tests with Nightwatch? No ? Should we run `npm install` for you after the project has been created? (recommended) no
修改项目\build\utils.js,防止npm run build 404报错

'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
//否则引入图标会报错
publicPath:'../../'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}
运行和打包项目
npm run dev/build
上线
当我们利用webpack打包出dist目录之后,就把这些静态文件全部部署、配置到Nginx/Apache这些web服务器的site目录下,就可以完成上线工作了。
如果服务器资源允许的话,我们还可以丰富一下我们的网站架构。
例如大型网站的常规架构:CDN+LVS/F5+Nginx反向代理实现负载均衡+数据读写分离+MySQL数据库集群+redis内存数据集群+kafka/RabitMQ消息队列集群,以提高网站的并发性能!
再利用gitlib+jekins+K8S实现CI CD
再加个zabbix/Prometheus监控把以上提到的组件全部监控起来!
使用element-ui
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库.
Mint UI 基于 Vue.js 的移动端组件库
这些组件库可以让我们的前端页面快速变得美观起来。
安装
cnpm i element-ui@2.13.2 -S
配置
我使用的是webpack-simple模板,默认没有对应的loader对.ttf的文件进行解析。

var path = require('path') var webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: {} // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } }, { test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/, loader: 'file-loader' } ] }, //添加插件 plugins: [ new HtmlWebpackPlugin({ //插件执行运行元素索引参照物 template: './index.html' }) ], resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true }, performance: { hints: false }, devtool: '#eval-source-map' }; if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }
使用
1.在main.js中全局引入element-ui
为什么我们可以在自己写的任意vue组件中引入element-ui中实现的组件呢,就是因为我们通过use把这些别人写的element-ui组件,做成了全局组件。可以在任意组件导入。
当我们导入element-ui提供的组件后它就是我们当前组件的子组件,父组件通过子组件设置的属性,传值。

import Vue from 'vue' import App from './App' //nodejs中导入包的机制:从router文件夹中自动查找index.js文件 import router from './router' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: '#app', router, components: {App}, template: '<App/>' });
当在min.js use("element-ui")之后它官网上的每1个组件都已全局定义好,我们只需要在自己定义组件里面直接使用即可。

<template> <div id="app"> <el-container style="height: 500px; border: 1px solid #eee"> <!--侧边栏区域开始 --> <el-aside width="200px" style="background-color: rgb(238, 241, 246)"> <el-menu :default-openeds="['1', '3']"> <el-submenu index="1"> <template slot="title"><i class="el-icon-message"></i>导航一</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="1-1">选项1</el-menu-item> <el-menu-item index="1-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="1-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="1-4"> <template slot="title">选项4</template> <el-menu-item index="1-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="2"> <template slot="title"><i class="el-icon-menu"></i>导航二</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="2-1">选项1</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="2-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="2-4"> <template slot="title">选项4</template> <el-menu-item index="2-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="3"> <template slot="title"><i class="el-icon-setting"></i>导航三</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="3-1">选项1</el-menu-item> <el-menu-item index="3-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="3-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="3-4"> <template slot="title">选项4</template> <el-menu-item index="3-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> </el-menu> </el-aside> <!--侧边栏区域结束--> <el-container> <!--header区域开始 --> <el-header style="text-align: right; font-size: 12px"> <el-dropdown> <i class="el-icon-setting" style="margin-right: 15px"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>查看</el-dropdown-item> <el-dropdown-item>新增</el-dropdown-item> <el-dropdown-item>删除</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>王小虎</span> </el-header> <!--header结束 --> <!--内容区域开始 --> <el-main> <el-table :data="tableData"> <el-table-column prop="date" label="日期" width="140"> </el-table-column> <el-table-column prop="name" label="姓名" width="120"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> </el-table> </el-main> <!--内容区域结束 --> </el-container> </el-container> </div> </template> <style> .el-header { background-color: #B3C0D1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style> <script> export default { name: 'App', data() { const item = { date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }; return { //填充table tbody的内容 tableData: Array(20).fill(item) } } }; </script>
3.使用echarts

<template> <div> <el-row :gutter="120"> <div :style="{height:'600px',width:'100%'}" ref="ChineseMapEchart"></div> </el-row> <el-row :gutter="120"> <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM --> <div id="pieChartOfAlarmDevice" style="float:left;width:100%;height:600px"></div> </el-row> <el-row :gutter="120"> <div ref="barEchart" style="width:100%;height:600px"></div> </el-row> </div> </template> <script> import axios from "axios"; import echarts from "echarts"; import china from "echarts/map/js/china.js"; require("echarts/lib/chart/pie"); require("echarts/lib/component/tooltip"); require("echarts/lib/component/title"); export default { data() { return { chart: null, ChineseAlarmsOption: { title: { text: "全国报警情况概览", //主标题 x: "center" }, // 进行相关配置 backgroundColor: "#02AFDB", tooltip: {}, // 鼠标移到图里面的浮动提示框 dataRange: { show: false, min: 0, max: 1000, text: ["High", "Low"], realtime: true, calculable: true, color: ["orangered", "yellow", "lightskyblue"] }, geo: { // 这个是重点配置区 map: "china", // 表示中国地图 roam: true, label: { normal: { show: true, // 是否显示对应地名 textStyle: { color: "rgba(0,0,0,0.4)" } } }, itemStyle: { normal: { borderColor: "rgba(0, 0, 0, 0.2)" }, emphasis: { areaColor: null, shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 20, borderWidth: 0, shadowColor: "rgba(0, 0, 0, 0.5)" } } }, series: [] }, alarmTypeOption: { tooltip: {}, legend: { data: ["报警类型"], x: "center" //x轴方向对齐方式 }, xAxis: { data: [ "硬件故障", "网络故障", "用户登录/出", "设备重启", "链路故障", "其他" ] }, yAxis: {}, series: [], color: ["#66FF99"] }, alarmDeviceOption: { title: { text: "报警来源", //主标题 x: "center" //x轴方向对齐方式 }, tooltip: { trigger: "item", formatter: "{a} <br/>{b} : {c} ({d}%)" }, legend: { orient: "vertical", bottom: "bottom", data: ["交换机", "防火墙", "路由器", "UPS", "服务器"] }, series: [] } }; }, methods: { drawChineseMapEchart(chartData, _this) { // console.log(_this.userJson); let ChineseMapEchart = echarts.init(_this.$refs.ChineseMapEchart); //这里是为了获得容器所在位置 window.onresize = ChineseMapEchart.resize; _this.ChineseAlarmsOption.series = [ { type: "scatter", coordinateSystem: "geo" // 对应上方配置 }, { name: "报警次数", // 浮动框的标题 type: "map", geoIndex: 0, data: chartData.ChineseAlarmsOption } ]; ChineseMapEchart.setOption(_this.ChineseAlarmsOption); }, drawAlarmTypeOptionEchart(chartData, _this) { let alarmTypeChart = echarts.init(_this.$refs.barEchart); _this.alarmTypeOption.series = [ { name: "报警类型", type: "bar", data: [5, 20, 36, 10, 10, 20] } ]; alarmTypeChart.setOption(_this.alarmTypeOption); }, drawAlarmDeviceEchart(data, _this) { let alarmDeviceChart = echarts.init( document.getElementById("pieChartOfAlarmDevice") ); _this.alarmDeviceOption.series = _this.alarmDeviceOption.series = [ { name: "报警来源", type: "pie", radius: "60%", center: ["50%", "60%"], data: [ { value: 335, name: "交换机" }, { value: 310, name: "防火墙" }, { value: 234, name: "路由器" }, { value: 135, name: "UPS" }, { value: 1548, name: "服务器" } ], itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: "rgba(0, 0, 0, 0.5)" } } } ]; alarmDeviceChart.setOption(_this.alarmDeviceOption); }, getAllChartData() { axios .get("http://127.0.0.1:8001/api/operation/trap/summary/") .then(response => { let status = response.status; if (status === 200) { let data = response.data; let vueObj = this; this.drawChineseMapEchart(data, vueObj); this.drawAlarmDeviceEchart(data, vueObj); this.drawAlarmTypeOptionEchart(data, vueObj); } }) .catch(err => { console.log(`Get data from serverr faild:${err}`); }); } }, created() { this.getAllChartData(); }, beforeDestroy() { if (!this.chart) { return; } this.chart.dispose(); this.chart = null; } }; </script> <style scoped> div { border-top: 5px solid crimson; } </style>
4.table行/列合并

<template> <div id="app"> <el-container style="height: 500px; border: 1px solid #eee"> <!--侧边栏区域开始 --> <el-aside width="200px" style="background-color: rgb(238, 241, 246)"> <el-menu :default-openeds="['1', '3']"> <el-submenu index="1"> <template slot="title"> <i class="el-icon-message"></i>导航一 </template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="1-1">选项1</el-menu-item> <el-menu-item index="1-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="1-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="1-4"> <template slot="title">选项4</template> <el-menu-item index="1-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="2"> <template slot="title"> <i class="el-icon-menu"></i>导航二 </template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="2-1">选项1</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="2-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="2-4"> <template slot="title">选项4</template> <el-menu-item index="2-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="3"> <template slot="title"> <i class="el-icon-setting"></i>导航三 </template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="3-1">选项1</el-menu-item> <el-menu-item index="3-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="3-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="3-4"> <template slot="title">选项4</template> <el-menu-item index="3-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> </el-menu> </el-aside> <!--侧边栏区域结束--> <el-container> <!--header区域开始 --> <el-header style="text-align: right; font-size: 12px"> <el-dropdown> <i class="el-icon-setting" style="margin-right: 15px"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>查看</el-dropdown-item> <el-dropdown-item>新增</el-dropdown-item> <el-dropdown-item>删除</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>王小虎</span> </el-header> <!--header结束 --> <!--内容区域开始 --> <el-main> <el-table :data="tableData" :span-method="arraySpanMethod"> <el-table-column prop="date" label="日期" width="140"></el-table-column> <el-table-column prop="newName" label="新名称"></el-table-column> </el-table> </el-main> <!--内容区域结束 --> </el-container> </el-container> </div> </template> <style> .el-header { background-color: #b3c0d1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style> <script> export default { name: "App", data() { return { tableData: [ { date: "400年", name: "马文才", address: "河北保定" }, { date: "377", name: "梁山伯", address: "浙江绍兴" }, { date: "377", name: "祝英台", address: "浙江绍兴" } ] }; }, methods: { //合并列 arraySpanMethod({ row, column, rowIndex, columnIndex }) { console.log(row, column, rowIndex, columnIndex) row.newName=this.tableData[rowIndex].address+"======"+this.tableData[rowIndex].name } } }; </script>
5.批量删除和分页

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>学生管理系统</title> <link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css"> <script src="js/vue.js"></script> <script src="element-ui/lib/index.js"></script> <script src="js/axios-0.18.0.js"></script> </head> <body> <div id="div"> <div style="float: left;"> <el-button type="primary" @click="showAddStu()">添加学生</el-button> <el-button type="danger" @click="batchDelete()">批量删除</el-button> </div> <el-table :data="pagination.list" ref="multipleTable" @selection-change="handleSelectionChange" border> <el-table-column type="selection" prop="id" label="学号" width="120"></el-table-column> <el-table-column prop="name" label="姓名" width="120"></el-table-column> <el-table-column prop="birthday" label="生日" width="140"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> <el-table-column label="操作" width="180"> <template slot-scope="props"> <el-button type="warning" @click="showEditStu(props.row)">编辑</el-button> <el-button type="danger" @click="deleteStu(props.row)">删除</el-button> </template> </el-table-column> </el-table> <el-dialog title="添加学生信息" :visible.sync="dialogTableVisible4Add" @close="resetForm('addForm')"> <el-form :model="addFormData" ref="addForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="学生学号"> <el-input v-model="addFormData.id"></el-input> </el-form-item> <el-form-item label="学生姓名"> <el-input v-model="addFormData.name"></el-input> </el-form-item> <el-form-item label="学生生日"> <el-input v-model="addFormData.birthday" type="date"></el-input> </el-form-item> <el-form-item label="学生地址"> <el-input v-model="addFormData.address"></el-input> </el-form-item> <el-form-item align="right"> <el-button type="primary" @click="saveStu()">添加</el-button> <el-button @click="resetForm('addForm')">重置</el-button> </el-form-item> </el-form> </el-dialog> <el-dialog title="编辑学生信息" :visible.sync="dialogTableVisible4Edit" @close="resetForm('editForm')"> <el-form :model="editFormData" ref="editForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="学生学号"> <el-input v-model="editFormData.id" disabled></el-input> </el-form-item> <el-form-item label="学生姓名"> <el-input v-model="editFormData.name"></el-input> </el-form-item> <el-form-item label="学生生日"> <!--v-model : 双向绑定 --> <el-input v-model="editFormData.birthday" type="date"></el-input> </el-form-item> <el-form-item label="学生地址"> <el-input v-model="editFormData.address"></el-input> </el-form-item> <el-form-item align="right"> <el-button type="warning" @click="editStu()">修改</el-button> </el-form-item> </el-form> </el-dialog> <el-pagination layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pagination.pageNum" :page-sizes="[2,4,6]" :page-size="pagination.pageSize" :total="pagination.total"> </el-pagination> </div> </body> <script> new Vue({ el: "#div", data: { dialogTableVisible4Add: false, //添加窗口显示状态 dialogTableVisible4Edit: false, //编辑窗口显示状态 addFormData: {},//添加表单的数据 editFormData: {},//编辑表单的数据 idList: [], //批量删除参数列表 pagination: { pageNum: 1, //当前页 pageSize: 2, //每页显示条数 total: 0, //总条数 pages: 0, //总页数 list: [],//当前页数据集合 }, }, methods: { //查询列表 findList() { axios.get("/studentServlet", { params: { action: "findByPage", pageNum: this.pagination.pageNum, pageSize: this.pagination.pageSize } }).then(response => { this.pagination = response.data; }) }, //分页-设置每1页的大小 handleSizeChange(pageSize) { this.pagination.pageSize = pageSize; this.findList() }, //分页-点击获取当前页码 handleCurrentChange(pageNum) { this.pagination.pageNum = pageNum; this.findList() }, //打开添加框 showAddStu(row) { this.dialogTableVisible4Add = true; console.log("打开添加框"); }, //打开编辑框 showEditStu(row) { this.dialogTableVisible4Edit = true; axios.get("/studentServlet?action=findById&id=" + row.id).then( response => { this.editFormData = response.data; } ) }, //新增学生 saveStu() { axios.post("/studentServlet?action=save", this.addFormData).then( response => { if (response.data == "OK") { //清空添加对象 this.addFormData = {}; //关闭对话框 this.dialogTableVisible4Add = false; //重新加载页面 this.findList(); } } ) }, //更新学生 editStu() { axios.post("/studentServlet?action=update", this.editFormData).then( response => { if (response.data == "OK") { //清空添加对象 this.edFormData = {}; //关闭对话框 this.dialogTableVisible4Edit = false; //重新加载页面 this.findList(); } } ) }, //删除学生 deleteStu(row) { axios.get("/studentServlet?action=delete&id=" + row.id).then( response => { if (response.data == "OK") { this.findList(); } } ) }, //批量删除-批量选中 handleSelectionChange(rowList) { this.idList = rowList; }, //批量删除 batchDelete() { let ids = ''; this.idList.forEach(row => { if (ids.length === 0) { ids += row.id + "," } else { ids += row.id + "," } }); //去除字符串 最后1个逗号 ids = ids.substring(0, ids.length - 1); axios.get("/studentServlet", { params: { action: "batchDelete", ids: ids } }).then(response => { if (response.data = "ok") { this.findList(); this.idList = []; } }) }, //重置form表单 resetForm(addForm) { //双向绑定,输入的数据都赋值给了formData, 清空formData数据 this.addformData = {}; this.editFormData = {}; }, }, created() { //查询列表 this.findList(); } }); </script> </html>
使用axios
jQuery通过ajax完成前后端交互,而vue使用axios。 Axios 是一个基于 JavaScript ES6 promise 的 HTTP 库,在浏览器和node.js都可以使用。
axios发送get请求
在很长一段时间里我使用axios时,把v-model的value的dict遍历一下,然后自拼接url,没有使用param参数。
vue前端

import Vue from 'vue' import App from './App.vue' //nodejs中导入包的机制:从router文件夹中自动查找index.js文件 import router from './router' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import Axios from 'axios' Vue.use(ElementUI); Vue.config.productionTip = false; //扩展vue原型 挂载axios:基于vue开发的第三方依赖使用Vue.use,不是基于vue开发的项目就扩展vue原型 Vue.prototype.$https=Axios; Axios.defaults.baseURL='http://127.0.0.1:8000/' /* eslint-disable no-new */ new Vue({ el: '#app', router, components: {App}, template: '<App/>' });

<template> <div id="app"> <div class="header"> <ul> <li v-for="(item) in navList" :key="item.id"> <router-link :to="{name:item.name}">{{item.title}}</router-link> </li> </ul> </div> <router-view></router-view> </div> </template> <script> export default { name: "App", data() { return { navList: [ { id: 1, title: "首页", name: "Home" }, { id: 2, title: "免费课程", name: "Course" } ] }; } }; </script> <style> </style>

<template> <div> <div class="header"> <span v-for="(item,index) in CategoryList" :key="index" :class="{active:index==current}" @click="clicKHandle(index,item.name)" >{{item.name}}</span> </div> <div class="body"> <ul> <li v-for="(item,index) in CourseList" :key="index">{{item.name}}</li> </ul> </div> </div> </template> <script> export default { name: "Home", data() { return { current: 0, current_categroy: "Python", CategoryList: [], CourseList: [] }; }, methods: { //获取分类列表 getCategoryList() { this.$https .get("categoryList/") .then(response => { let status = response.status; if (status === 200) { let data = response.data.data; this.CategoryList = data; } }) .catch(err => { console.log(`Get data from serverr faild:${err}`); }); }, //点击分类添加active属性变色 clicKHandle(index, category) { this.current = index; this.current_categroy = category; this.getCourseList(); }, //获取详细课程列表 getCourseList() { this.$https .get(`${this.current_categroy}/courseList/`) .then(response => { let status = response.status; if (status === 200) { let data = response.data; this.CourseList = data; } }) .catch(err => { console.log(`get data from server ${err}`); }); } }, created() { this.getCategoryList(); this.getCourseList(); } }; </script> <style scoped> span { padding: 0 20px; } span.active { color: aquamarine; } </style>
Django后端

"""luffy_city URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^categoryList/$', views.categoryList), url(r'^(?P<category>\w+)/courseList/$', views.courseList), ]

import json from django.shortcuts import HttpResponse from django.views.decorators.csrf import csrf_exempt categoryListData = {"error_no": True, 'data': [ {'id': 1, 'name': "Python", "category": 1}, {'id': 2, 'name': "Linux基础", "category": 4}, {'id': 3, 'name': "前端", "category": 2}, {'id': 4, 'name': "Python进阶", "category": 1}, {'id': 5, 'name': "UI", "category": 5}, {'id': 6, 'name': "工具", "category": 1}, ]} courseListData = { "Python": [ {"id": 1, "name": "Python开发21天入门"}, {"id": 2, "name": "Django web框架"}, {"id": 3, "name": "Flash源码分析"}, {"id": 4, "name": "Python socket编程"}, ], "Linux基础": [ {'id': 1, "name": "14天Linux入门"}, {'id': 2, "name": "Shell编程实战"}, {'id': 3, "name": "4小时计算机预科"}, ], "前端": [ {'id': 1, "name": "WEB开发之HTML篇"}, {'id': 2, "name": "WEB开发之CSS篇"}, {'id': 3, "name": "JavaScript编程基础"}, {'id': 4, "name": "Vue框架深入学习"}, ], "Python进阶": [ {'id': 1, "name": "Pands模块的学习"}, {'id': 2, "name": "AI数学课"}, {'id': 3, "name": "2周搞定人工智能数学基础"}, ], "UI": [ {'id': 1, "name": "LayUI的学习"}, {'id': 2, "name": "Bootstrap的学习"}, {'id': 3, "name": "楠哥的UI实战课"}, ], "工具": [ {'id': 1, "name": "Git入门实战"}, {'id': 2, "name": "yum linux软件管理"}, {'id': 3, "name": "npm nodejs课程"}, ] } @csrf_exempt def categoryList(request): obj = HttpResponse(json.dumps(categoryListData)) obj['Access-Control-Allow-Origin'] = "*" return obj @csrf_exempt def courseList(request,category): obj = HttpResponse(json.dumps(courseListData[category],ensure_ascii=False)) print(courseListData[category]) obj['Access-Control-Allow-Origin'] = "*" return obj
axios post json/from数据

// axios.defaults.headers = { // //请求的数据类型 form // //"Content-type": "application/x-www-form-urlencoded" // //请求的数据类型 json // "Content-type": "application/json;charset=utf-8" // }; axios({ method: "post", headers: {"Content-Type": "application/json;charset=utf-8"}, url: "http://127.0.0.1:8000/categoryList/", data: data }) .then(response => { let status = response.status; if (status === 200) { let data = response.data.data; console.log(data); } }) .catch(err => { console.log(`Get data from serverr faild:${err}`); });
仅用于测试不安全,如果获取csrf-token需要先发get请求,然后从响应头的cokie里获取到django的csrf-token。

@csrf_exempt def categoryList(request): print( request.body.decode('utf-8')) obj = HttpResponse(json.dumps(categoryListData)) obj['Access-Control-Allow-Origin'] = "*" obj['Access-Control-Allow-Headers'] = "*" return obj
axios并发请求
同时发送多个请求,需要注意的是这些请求是并发的, 没有到达server端的先后顺序

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>axios的基本使用</title> <script src="./node_modules/vue/dist/vue.js"></script> <script src='./node_modules/axios/dist/axios.js'></script> </head> <body> <div id="app"> </div> </body> <script> let App = { name: 'APP', template: `<div> {{response1}} {{response2}} {{response3}} {{response4}} <button @click='sendAajx'>发送ajax</button> </div>`, data() { return { response1: '', response2: '', response3: '', response4: '' } }, methods: { sendAajx() { //设置默认url axios.defaults.baseURL = ' http://127.0.0.1:8000/' //get请求:params可以设置url参数,不需要做url拼接了。 var request1 = axios.get('categoryList/', { params: { id: 2020 } }); //post请求 var request2 = axios.post('categoryList/', data = { method: '我是post请求', id: 2020, name: 'Tom', age: 18 }); //put请求 let request3 = axios({ method: 'put', url: 'categoryList/', data: { method: '我是put请求', id: 2020, newName: 'Jack' } }); //delete请求 let request4 = axios({ method: 'delete', url: 'categoryList/', data: { method: '我是delete请求', id: 2020 } }) //发送合并请求:注意以上的请求到达server端是没有顺序的 axios.all([request1, request2, request3, request4]).then( axios.spread((response1, response2, response3, response4) => { // console.log(response1.data.data) this.response1 = response1.data.data this.response2 = response2.data.data this.response3 = response3.data.data this.response4 = response4.data.data }) ).catch(err => { //其中1个请求失败就会被cath到error console.log(err) }) } } }; new Vue({ el: '#app', data() { return {} }, template: `<App/>`, components: { App } }) </script> </html>
axios的钩子函数
上面说到当我们在Vue中发生路由跳转的时候有全局路由守卫中间件可以做权限控制,当我们发送ajax请求的时候axios中也有一些中间件可以控制我们请求和响应。

import axios from 'axios' import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url withCredentials: true, // 跨域请求时发送cookies timeout: 0 // request timeout }) // request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // let each request carry token // ['X-Token'] is a custom headers key // please modify it according to the actual situation config.headers['X-Token'] = getToken() } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { const res = response.data // if the custom code is not 200, it is judged as an error. if (res.code !== 200) { Message({ message: res.msg || 'Error', type: 'error', duration: 5 * 1000 }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { // to re-login MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) } return Promise.reject(new Error(res.msg || 'Error')) } else { return res } }, error => { console.log('err' + error) // for debug Message({ message: error, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service
1.interceptors 请求、响应拦截器
当我们发送请求之前和server端响应数据之后,可以对请求和响应进行拦截,比如检查token

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>请求拦截器</title> <script src="./node_modules/vue/dist/vue.js"></script> <script src="./node_modules/axios/dist/axios.js"></script> <style> .loading1 { width: 50px; height: 40px; margin: 60px auto; text-align: center; } .loading1 span { width: 5px; height: 100%; display: inline-block; background: #67CF22; ; animation: mymove 1.2s infinite ease-in-out; -webkit-animation: mymove 1.2s infinite ease-in-out; /* mymove代表动画名字 1.2s代表执行时间 infinite表示一直循环执行 ease-in-out表示先慢后快的缓动 */ } .loading1>span:nth-child(2) { -webkit-animation-delay: -1.0s; animation-delay: -1.0s; } .loading1>span:nth-child(3) { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .loading1>span:nth-child(4) { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } .loading1>span:nth-child(5) { -webkit-animation-delay: -0.7s; animation-delay: -0.7s; } @keyframes mymove { 0% { transform: scaleY(0.4); } 25% { transform: scaleY(1.0); } 50% { transform: scaleY(0.4); } 75% { transform: scaleY(0.4); } 100% { transform: scaleY(0.4); } } @-webkit-keyframes mymove { 0% { transform: scaleY(0.4); } 25% { transform: scaleY(1.0); } 50% { transform: scaleY(0.4); } 75% { transform: scaleY(0.4); } 100% { transform: scaleY(0.4); } } </style> </head> <body> <div id="app"> <div> <button @click='sendAjax'>发送ajax</button> </div> </div> </body> <script> let App = { name: 'App', template: `<div> <div class="loading1" v-show='isLoading'> <span></span> <span></span> <span></span> <span></span> <span></span> </div> <button @click='sendAjax'>发送ajax</button> </div>`, data() { return { isLoading: false } }, methods: { sendAjax() { axios.defaults.baseURL = ' http://127.0.0.1:8000/' //添加请求拦截器 axios.interceptors.request.use( (config) => { console.log(config) //显示加载样式 this.isLoading = true //每次请求之前检查一下客户端是否有token let token = localStorage.getItem('token') console.log(token) if (token) { config.headers['token'] = token; } return config; }, function (err) { console.log(err) return Promise.reject(err) } ); //添加响应拦截器 axios.interceptors.response.use( (response) => { console.log(response) let token = response.data.token console.log(response.data) //隐藏加载样式 this.isLoading = false //每次服务器响应token之后:在客户端设置 token if (token) { localStorage.setItem('token', token); } return response }, function (err) { console.log(err) return Promise.reject(err) } ); axios.get('categoryList/').then(response => { console.log(response.data) }).catch(err => { console.log(err) }) } } } new Vue({ el: '#app', data() { return {} }, template: `<App/>`, components: { App } }) </script> </html>
2.transform 请求、响应改造
使用Vue是数据驱动视图的过程, 但是后端和视图之间的数据结构可能会存在一些偏差,这时我们就可以通过axios的钩子函数,对request、response进行改造(transform)。
在我们使用axios发送请求和接收响应的这个过程中,axios内置了很多钩子函数,以便我们对transform(改造)request和response的数据结构。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Axios的钩子函数</title> <script src="./node_modules/vue/dist/vue.js"></script> <script src='./node_modules/axios/dist/axios.js'></script> </head> <body> <div id="app"> </div> </body> <script> let App = { name: 'App', template: ` <div> <p>{{response1}}</p> <button @click='sendAajx'>发送ajax</button> </div> `, data() { return { response1: '' } }, methods: { sendAajx() { axios.defaults.baseURL = ' http://127.0.0.1:8000/' axios.post('categoryList/', { name: "Jake" }, { params: { id: 1 }, //transform:改变外观、改变形态 //对请求体:request进行transform操作,所以不支持get请求。 transformRequest: [function (data) { console.log('=======', typeof (data)) return data }], //对响应体:response进行transform操作 transformResponse: [function (data) { console.log("此时reponse还是json字符串:", typeof (data)) data = JSON.parse(data) data['name'] = '张根' data = JSON.stringify(data) return data }] }, ).then(response => { console.log(response.data) this.response1 = response.data }).catch(err => { console.log(err) }) } } } new Vue({ el: '#app', template: `<App/>`, components: { App } }) </script> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Axios的钩子函数</title> <script src="./node_modules/vue/dist/vue.js"></script> <script src='./node_modules/axios/dist/axios.js'></script> </head> <body> <div id="app"> </div> </body> <script> let App = { name: 'App', template: ` <div> <p>{{response1}}</p> <button @click='sendAajx'>发送ajax</button> </div> `, data() { return { response1: '' } }, methods: { sendAajx() { axios.defaults.baseURL = ' http://127.0.0.1:8000/' axios.get('categoryList/', { params: { id: 1 }, //transform:改变外观、改变形态 transformResponse: [function (data) { console.log("此时reponse还是json字符串:", typeof (data)) //对response进行transform操作 data = JSON.parse(data) data['name'] = '张根' data = JSON.stringify(data) return data }] }, ).then(response => { console.log(response.data) this.response1 = response.data }).catch(err => { console.log(err) }) } } } new Vue({ el: '#app', template: `<App/>`, components: { App } }) </script> </html>
封装axios
在工作中我们可以高度封装axios,封装成了Python的requests模块的样子。

import axios from 'axios' import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url withCredentials: true, // 跨域请求时发送cookies timeout: 0 // request timeout }) // request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // let each request carry token // ['X-Token'] is a custom headers key // please modify it according to the actual situation config.headers['X-Token'] = getToken() } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { const res = response.data // if the custom code is not 200, it is judged as an error. if (res.code !== 200) { Message({ message: res.msg || 'Error', type: 'error', duration: 5 * 1000 }) // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { // to re-login MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { confirmButtonText: 'Re-Login', cancelButtonText: 'Cancel', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) } return Promise.reject(new Error(res.msg || 'Error')) } else { return res } }, error => { console.log('err' + error) // for debug Message({ message: error, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service
前端使用

request({ url: 'userRights/region/', withCredentials: true, method: 'get', baseURL: 'http://127.0.0.1/', params: { name: 'martin', age: 18 } }).then(res => { console.log('----->', res) }).catch((err) => { console.log('=====>', err) })
后端配合

def region(request): if request.method=='GET': print(request.META.get('HTTP_X_TOKEN')) obj=HttpResponse(json.dumps({"name":'martin',"code":200})) obj['Access-Control-Allow-Origin'] = "http://local.jd.com:9528" obj["Access-Control-Allow-Methods"] = "*" obj['Access-Control-Allow-Headers'] = "x-token" obj['Access-Control-Allow-Credentials'] = 'true' return obj
Vuex
在Vue组件化开发的过程中, 组件之间如何共享数据就显得尤为重要。
Vuex就像前端中的数据库,各个组件都可以添加、修改这个数据库中的数据。解决了各个组件间数据实时共享的问题。
安装Vuex
npm install vuex --save
Vuex架构
Vuex由5个重要组件构成分别是Action(动作)、Mutation(改变)、State(状态)、Getters、Modules
一旦store挂载到vue对象之后,我就可以在任意组件中通过computed this.$store.state.num,使用store中state: { num: 1 }的数据。
如果是异步操作:需要先dishpath到actions中声明的方法,然后早action中声明的方法再去commit 执行在mutation中声明的方法。否则会导致数据紊乱。
注意:如果是异步操作,一定要声明在actions中例如(ajax/settimeout)。
如果是同步操作:可以直接在组件中commit执行mutation中声明的方法。

<template> <div> <h2>我是子组件中的{{MySonNum}}</h2> <button @click="addNum">同步修改</button> <button @click="addAsyncNum">异步修改</button> </div> </template> <script> export default { name: "Son", methods: { addNum() { //在组件中无法直接修改state中声明的状态属性 this.$store.dispatch("setActionNumber", 1); }, addAsyncNum() { this.$store.dispatch("setActionNumberAsync", 5); } }, computed: { //组件通过computed和store中的声明的值进行关联。 MySonNum: function() { return this.$store.state.num; } } }; </script> <style> </style>

import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'http://127.0.0.1:8000/' import Vuex from 'vuex' //使用Vuex Vue.use(Vuex) Vue.use(router) Vue.config.productionTip = false const store = new Vuex.Store({ state: { num: 1 }, actions: { //actions中提交的是mutation,而不是直接修改状态。 setActionNumber(content, number) { content.commit("setMutationNumber",number) }, setActionNumberAsync(content, number) { setTimeout(() => { content.commit("setMutationNumberAsync",number) }, 1); } }, mutations: { //通过commit(setNum,number)在组件中触发在mutations中声明的方法 setMutationNumber(state, number) { console.log(number); state.num += number }, //异步方法 setMutationNumberAsync(state, number) { //setTimeout异步操作 setTimeout(() => { state.num += number console.log(state.num) }, 1); } }, }) /* eslint-disable no-new */ new Vue({ el: '#app', router, store,//挂载vuex template: '<App/>', render: h => h(App) })
把局部组件声明为全局组件
在main.js中声明之后即可在在任意组件使用。
import Son from './components/Son.vue'
Vue.component(Son.name,Son)
axios使用指南
ElementUI 2.13.2
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南