Vue全家桶

前言

尤其是大公司1个部门中可能有擅长Java/Node/Python/Php/Go的,有的擅长同1种后端语言而擅用不同的web框架。

那么如何在不做人员优化的前提下,凝聚起这些不同的研发力量,为1个共同的项目添砖加瓦呢?

我们可以采用前后端分离的开发模式,大家后端都用自己擅长的后端语言玩,但共用1个前端框架。

 

ECMAScript6的基本语法

Vue是一个渐进式的JavaScript框架,渐进式我暂时还没有理解,但是如果是JavaScript框架肯定要熟悉JavaScript。

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>
let和var的区别

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>
es6中的类

对象

在对象声明时 使用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>
es6对象this指向问题
<!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=" 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>
妙用箭头函数

MVVM设计模式

MVVM设计模式是对MVC/MTV(Django模板语言)设计模式的一种优化,我们使用Django的模板语法{{}}/for/if直接去渲染Dom虽然非常方便,但是项目功能复杂之后,很难进行扩展。

所以有人提出,把模板语法渲染、Dom显示的功能和后端进行解耦, 划分到view视图层。

然后把视图层(也就是MTV中view)在进行拆分,把它划分为M、V、VM通过数据来驱动视图,通过修改视图驱动数据变化。

M:Vue的data中保存的数据属性

VM:Vue的实例化对象,VM 用来连接数据和视图, 视图的输入框绑定了v-model, 用户输入后会改变data;,

V:视图这里的视图不是Django中的视图函数,而是用户看到的视图,也就是html。

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="300px;height: 300px" v-bind:src="imgSrc" v-bind:alt="imgAlt" v-bind:id="imgId" v-bind:class="imgClass"  >

 简写形式

  <img style="300px;height: 300px" :src="imgSrc" :alt="imgAlt" :id="imgId">

视图层(最终显示的标签)view

<img src="./img/timg%20(1).jpg" alt="美女" id="img1" class="beauty" style=" 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

双向数据绑定:就是数据由Vue对象的data 渲染到标签(视图)、用户修复标签(视图改变Vue对象的data。整个过程就如同input标签显示操作和提交到后台。

所以v-model应用于input系列标签,例如:input/textare/select。有了v-model指令我们就可以轻松完成对input标签值的获取

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-model数据双向绑定</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">
    <!--双向数据绑定使用v-model -->
    <label for="name">家庭住址:</label>
    <input type="text" v-model.trim="msg" id="name">
    <p>籍贯:{{ msg }}</p>
    <!-- textarea内容改变input也随之改变-->
    <textarea name="" id="" cols="30" rows="10" v-model.trim="msg"></textarea>
    <br>
    <br>
    <!--单个复选框,绑定到布尔值:-->
    <label for="checkbox">是否健康:{{ checked }}</label>
       <input id="checkbox" type="checkbox" v-model="checked">
    <!--多个复选框,绑定到同1个数组-->
    <br>
    <br>
    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    <label for="jack">Jack</label>
    <input type="checkbox" id="john" value="John" v-model="checkedNames">
    <label for="john">John</label>
    <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
    <label for="mike">Mike</label>
    <br>
    <span>选中的:{{ checkedNames }}</span>
    <br>
    <br>
    <!--radio互斥标签  -->
    <div id="example-4">
        <input type="radio" id="one" value="One" v-model="picked">
        <label for="one">One</label>
        <br>
        <input type="radio" id="two" value="Two" v-model="picked">
        <label for="two">Two</label>
        <br>
         <input type="radio" id="three" value="Three" v-model="picked">
        <label for="two">Two</label>
        <br>
        <span>Picked: {{ picked }}</span>
    </div>
    <br>
         <br>
    <!--select单选-->
    <select name="" id="" v-model="select_one">
        <option disabled value=""></option>
        <option value=""></option>
        <option value="1">1号</option>
        <option value="2">2号</option>
        <option value="4">3号</option>
    </select>
    <br>
    <span>选中的:{{ select_one }}</span>
    <br>
    <br>

    <!--多选select-->
       <select name="" id="" multiple v-model="select_mutill">
        <option disabled value=""></option>
        <option value=""></option>
        <option value="王菲">王菲</option>
        <option value="关凌">关凌</option>
        <option value="凤姐">凤姐</option>
        <option value="贾玲">贾玲</option>
        <option value="马蓉">马蓉</option>
    </select>
    <br>
    <span>选中的人们:{{ select_mutill}}</span>
    <br>
    <br>
    <!--按回车键msg信息修改-->
    <label for="posrtion">确认住址:</label>
    <input type="text" v-model.trim.lazy="msg" id="posrtion">
     <br>
     <br>
    <!--清除前后空格 -->
    <label for="salary">工资:</label>
    <input type="number" v-model.number="salary"  id="salary">

</div>

</body>
<script>
    new Vue({
        el: "#app",
        data() {
            return {
                msg: "",
                checked: false,
                checkedNames: [],
                picked:'',
                select_one: '',
                select_mutill:[],
                salary:19
            }
        },
    })
</script>
</html>
input数据双向绑定

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>
main.js
<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>
App.vue

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

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=" 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=" 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>
watch侦听器

2.Vue计算属性(computed)

计算属性可以同时自动监听Vue对象中多个数据属的变化时间。

<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>
template属性和el属性的优先级

在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>
slot内容分发

父往子组件传值(绑定自定义属性)

模块化开发虽然通过解耦的方式,增加了代码的灵活性,但是各个组件之间如何传输数据的问题就会显现出来。

如果父组件通过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>
bus传值

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

 

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>
Vue组件的钩子函数
keep-alive内置组件

如果每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>
keep-alive状态保存

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的实现原理

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>
VueRouter的基本使用

命名路由

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"},

      ]
    }

  ]
})
router/index.js
<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>
src/User/user.vue
<template>
  <div>
      <h2>{{ $route.params.id }}的文章</h2>
  </div>
</template>

<script>
export default {
    
}
</script>

<style>

</style>
src/User/user_post.vue
<template>
  <div>
      <h2>{{ $route.params.id }}的头像</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
src/User/user_proflie.vue

子路由和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>
meta结合全局路由守卫

获取原生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>
ref属性

传统的web开发模式

1.传统的web开发模式面临的困境

我1个人在传统前端开发模式下滞后了很久,久到自以为全栈。我使用Django的模板语言,遇到项目里需要的UI插件我就自己去官网下载文件,然后在html文件中<script>、<link>、DOM操作。

但是......当这些第三方依赖库越多,调试时出现页面奔溃的概率就越高。

因为你不知道别人写的这些依赖库之间的依赖的版本、依赖顺序是什么样的 ,你只能自己去不断尝试。

总结下来传统的前端开发模式会面临以下几种问题:

手动下载第三方依赖包

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;
time.js

导入

//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
index.js

node环境运行

D:Vue学习day04
odejs模块>node index.js
{ name: '艾青' }
[ '艾青', '陈万松', '王海涛' ]
2

ES6的module:中的export default 和 import的语法,也支持模块化开发。但是无法直接在浏览器运行,需要借助webpack处理js在浏览器中的兼容性问题。

 

 把下面2个JavaScript文件打包,module.js

var p={
    name:"张三"
};
export default p;
module.js

 依赖main.js文件

import person from './module.js'

console.log(person.name);

alert(1);
main.js

输出

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

/***/ })
/******/ ]);
boundle.js

webpack运行

D:	rap_serverVue学习day04es6 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>
index.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"
}
npm init --yes

在项目中局部安装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"
  }
}
package.json

线下配置

module.exports = {
    entry: {'main': './main.js'},
    output: {'filename': './bundle.js'},
    watch:true, //自定检查代码是否更新
};
webpack.dev.config.js

线上环境配置

module.exports = {
    entry: {'main': './main.js'},
    output: {'filename': './bundle.js'},
};
webpack.product.config.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.dev.config.js

目录整理

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'
        })
    ]
};
webpack.dev.config.js

支持根据参照html自动在dist目录下生成、引入打包好的js文件

D:	rap_serverVue学习day0403-module_study_further>npm run dev

> 03-module_study_further@1.0.0 dev D:	rap_serverVue学习day0403-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'
        })
    ]
};
webpack.dev.config.js

支持项目自动运行在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"
  }
}
package.json

 

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

修改项目uildutils.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')
    })
  }
}
项目uildutils.js

运行和打包项目

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-ui提供各种丰富的基于vue实现的前端组件,让我可以让我们的前端页面变得更加美观、友好。

安装

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
    })
  ])
}
webpack.config.js

使用

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/>'
});
main.js

2.使用element-ui中的组件

当在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>
App.vue

3.使用echarts

<template>
  <div>
    <el-row :gutter="120">
      <div :style="{height:'600px','100%'}" ref="ChineseMapEchart"></div>
    </el-row>
    <el-row :gutter="120">
      <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
      <div id="pieChartOfAlarmDevice" style="float:left;100%;height:600px"></div>
    </el-row>
    <el-row :gutter="120">
      <div ref="barEchart" style="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>
SumaryAlarms.vue

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

使用axios

jQuery通过ajax完成前后端交互,而vue使用axios。 Axios 是一个基于 JavaScript ES6 promise 的 HTTP 库,在浏览器node.js都可以使用

axios发送get请求

 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/>'
});
main.js
<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>
App.vue
<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>
Home.vue

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),
]
urls.py
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
views.py

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
Django后端

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

把局部组件声明为全局组件

在main.js中声明之后即可在在任意组件使用。

import Son from './components/Son.vue'
Vue.component(Son.name,Son)

小马哥

axios使用指南

Vue脚手架

ElementUI 2.13.2

Django+Vue跨站请求配置

vue Router

vue官网

https://www.cnblogs.com/kingkongk/p/12982219.html

原文地址:https://www.cnblogs.com/sss4/p/13217357.html