Vue

第 0 章 Vue 介绍

0.0 开发工程发展历史

通过前面的介绍,我们对目前的项目工程化有了大体了了解,那么其中,在第二阶段的工程化演进中,有一个重要的工程设计理念诞生,他就是著名的 MVC 设计模式,简单点,MVC 其实就是为了项目工程化的一种分工模式;

MVC 中的最大缺点就是单项输入输出,所有的 M 的变化及 V 层的变化,必须通过 C 层调用才能展示;

为了解决相应的问题,出现了 MVVM 的设计思想,简单理解就是实想数据层与展示层的相互调用,降低业务层面的交互逻辑;后面再进行详细介绍;

0.1 Vue 介绍

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的 渐进式框架

注意:Vue 是一个框架,相对于 jq 库来说,是由本质区别的;

https://cn.vuejs.org/

Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器

0.2 Vue 初体验

直接下载引入:https://cn.vuejs.org/v2/guide/installation.html

CDN 引入:

js

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

最新版
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

CDN 加速: https://www.bootcdn.cn/

html

<body>
  <div id="div">
    { {user_name} }
  </div>
</body>

// 两种引入方式,任意选择
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script src="./vue.js"></script>

<script>
  var app = new Vue({
    el: "#div", // 设置要操作的元素
    // 要替换的额数据
    data: {
      user_name: "我是一个div"
    }
  });
</script>

0.3 学习 Vue

基础知识 –> 项目 –> 构建工具 –> Vue 其他相关技术

第 1 章 Vue 实例对象

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例 开始的:

js

var vm = new Vue({
  // 选项
});

html

<body>
  <div id="div">
    { {user_name} }
  </div>
</body>
<script src="./vue.js"></script>
<script>
  var app = new Vue({
    el: "#div", // 设置要操作的元素
    // 要替换的额数据
    data: {
      user_name: "我是一个div"
    }
  });

  // 打印Vue实例对象
  console.log(app);
</script>

通过打印实例对象发现,其中 el 被 Vue 放入了公有属性中,而 data 则被放入了 私有属性中,而 data 中的数据,需要被外部使用,于是 Vue 直接将 data 中的属性及属性值,直接挂载到 Vue 实例中,也就是说,data 中的数据,我们可以直接使用 app.user_name 直接调用;

js

var app = new Vue({
  el: "#div", // 设置要操作的元素
  // 要替换的额数据
  data: {
    user_name: "我是一个div",
    user: 222222
  }
});

console.log(app.user_name);

第 2 章 模板语法-插值

我们在前面的代码中,使用 { {} } 的形式在 html 中获取实例对象对象中 data 的属性值;

这种使用 { {} } 获取值得方式,叫做 插值插值表达式

2.1 文本

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:

html

<span>Message: { { ms g }}</span>

Mustache 标签将会被替代为对应数据对象上 msg 属性的值。无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。即便数据内容为一段 html 代码,仍然以文本内容展示

html

<body>
  <div id="div">
    文本插值 { {html_str} }
  </div>
</body>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      html_str: "<h2>Vue<h2>"
    }
  });
</script>

浏览器渲染结果:<div id="div">文本插值 <h2>Vue<h2></div>

打开浏览器的 REPL 环境 输入 app.html_str = '<s>vue</s>'

随机浏览器渲染结果就会改变: <div id="div">文本插值 <s>vue</s></div>

html

### 2.2 使用 JavaScript 表达式
迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定,Vue.js
都提供了完全的 JavaScript 表达式支持,但是不能使用 JS 语句;
(表达式是运算,有结果;语句就是代码,可以没有结果)

<body>
  <div id="div">
    { { u n > 3 ? '大' : '小'}} { { fu n() }}
  </div>
</body>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      un: 2,
      fun: () => {
        return 1 + 2;
      }
    }
  });
</script>

第 3 章 模板语法-指令

指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM;参考 手册API

html

<body>
  <div id="div">
    <p v-if="seen">现在你看到我了</p>
  </div>
</body>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      seen: false
    }
  });
</script>

这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。

3.1 v-text / v-html 文本

https://cn.vuejs.org/v2/api/#v-text

https://cn.vuejs.org/v2/api/#v-html

html

<body>
  <div id="div" { {class}}>
    <p v-text="seen"></p>
    <p v-html="str_html"></p>
  </div>
</body>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      seen: "<h1>Vue</h1>",
      str_html: "<h1>Vue</h1>",
      class: "dd"
    }
  });
</script>

注意:

  • v-text
    • v-text 和差值表达式的区别
      • v-text 标签的指令更新整个标签中的内容(替换整个标签包括标签自身)
      • 差值表达式,可以更新标签中局部的内容
  • v-html
    • 可以渲染内容中的 HTML 标签
    • 尽量避免使用,否则会带来危险(XSS 攻击 跨站脚本攻击)

HTML 属性不能用 { {}} 语 法

3.2 v-bind 属性绑定

https://cn.vuejs.org/v2/api/#v-bind

可以绑定标签上的任何属性。

动态绑定图片的路径

html

<img id="“app”" v-bind:src="src" />
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      src: "1.jpg"
    }
  });
</script>

绑定 a 标签上的 id

html

<a id="app" v-bind:href="'del.php?id=' + id">删除</a>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      id: 11
    }
  });
</script>

绑定 class

对象语法和数组语法

  • 对象语法 如果 isActive 为 true,则返回的结果为 <div id="app" class="active"></div> html <div id="app" v-bind:class="{active: isActive}"> hei </div> <script> var vm = new Vue({ el: "#app", data: { isActive: true } }); </script>
  • 数组语法 渲染的结果: <div id="app" class="active text-danger"></div> html <div id="app" v-bind:class="[activeClass, dangerClass]"> hei </div> <script> var vm = new Vue({ el: "#app", data: { activeClass: "active", dangerClass: "text-danger" } }); </script>

绑定 style

对象语法和数组语法

  • 对象语法 渲染的结果: <div id="app" style="color: red; font-size: 40px;">hei</div> html <div id="app" v-bind:style="{color: redColor, fontSize: font + 'px'}"> hei </div> <script> var vm = new Vue({ el: "#app", data: { redColor: "red", font: 40 } }); </script>
  • 数组语法 渲染结果:<div id="app" style="color: red; font-size: 18px;">abc</div>

html

<div id="app" v-bind:style="[color, fontSize]">abc</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      color: {
        color: "red"
      },
      fontSize: {
        "font-size": "18px"
      }
    }
  });
</script>

简化语法

html

<div id="app">
  <img v-bind:src="imageSrc" />
  <!-- 缩写 -->
  <img :src="imageSrc" />
</div>

<script>
  var vm = new Vue({
    el: "#app",
    data: {
      imageSrc: "1.jpg"
    }
  });
</script>

3.3 v-model 双向数据绑定

https://cn.vuejs.org/v2/api/#v-model

单向数据绑定

html

<div id="div">
  <input type="text" :value="input_val" />
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      input_val: "hello world "
    }
  });
</script>

浏览器渲染结果: <div id="div"><input type="text" value="hello world"></div>

通过浏览器 REPL 环境可以进行修改 app.input_val = 'Vue'

浏览器渲染结果: <div id="div"><input type="text" value="Vue"></div>

我们通过 vue 对象修改数据可以直接影响到 DOM 元素,但是,如果直接修改 DOM 元素,却不会影响到 vue 对象的数据;我们把这种现象称为 单向数据绑定

双向数据绑定

html

<div id="div">
  <input type="text" v-model="input_val" />
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      input_val: "hello world "
    }
  });
</script>

通过 v-model 指令展示表单数据,此时就完成了 双向数据绑定

不管 DOM 元素还是 vue 对象,数据的改变都会影响到另一个;

多行文本 / 文本域

html

<div id="div">
  <textarea v-model="inp_val"></textarea>
  <div>{ { inp_va l }}</div>
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      inp_val: ""
    }
  });
</script>

绑定复选框

html

<div id="div">
  吃饭:<input type="checkbox" value="eat" v-model="checklist" /><br />
  睡觉:<input type="checkbox" value="sleep" v-model="checklist" /><br />
  打豆豆:<input type="checkbox" value="ddd" v-model="checklist" /><br />
  { { checklis t }}
</div>

<script>
  var vm = new Vue({
    el: "#div",
    data: {
      checklist: ""
      // checklist: []
    }
  });
</script>

绑定单选框

html

<div id="app">
  男<input type="radio" name="sex" value="男" v-model="sex" /> 女<input
    type="radio"
    name="sex"
    value="女"
    v-model="sex"
  />
  <br />
  { {sex} }
</div>

<script>
  var vm = new Vue({
    el: "#app",
    data: {
      sex: ""
    }
  });
</script>

修饰符

.lazy - 取代 input 监听 change 事件

.number - 输入字符串转为有效的数字

.trim - 输入首尾空格过滤

html

<div id="div">
  <input type="text" v-model.lazy="input_val" />
  { {input_val} }
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      input_val: "hello world "
    }
  });
</script>

3.4 v-on 绑定事件监听

https://cn.vuejs.org/v2/api/#v-on

https://cn.vuejs.org/v2/guide/events.html

3.4.1 基本使用

html

<div id="app">
  <input type="button" value="按钮" v-on:click="cli" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      cli: function() {
        alert("123");
      }
    }
  });
</script>

上面的代码运行是没有问题的,但是,我们不建议这样做,因为 data 是专门提供数据的对象,事件触发需要执行的是一段代码,需要的是一个方法 (事件处理程序) ;

修改代码如下:

html

<div id="app">
  <!-- 使用事件绑定的简写形式 -->
  <input type="button" value="按钮" @click="cli" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    // 将事件处理程序写入methods对象
    methods: {
      cli: function() {
        alert("123");
      }
    }
  });
</script>

向事件处理器中传参

html

<div id="app">
  <!-- 直接调用传参即可 -->
  <input type="button" value="按钮" @click="cli(1,3)" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      // 接受参数
      cli: function(a, b) {
        alert(a + b);
      }
    }
  });
</script>

而此时,如果在处理器中需要使用事件对象,则无法获取,我们可以用特殊变量 $event 把它传入方法

<input type="button" value="按钮" @click="cli(1,3,$event)">

js

methods: {
    // 接受参数
    cli: function (a,b,ev) {
        alert(a+b);
        console.log(ev);
    }
}

3.4.2 事件修饰符

原生 JS 代码,想要阻止浏览器的默认行为(a 标签跳转、submit 提交),我们要使用事件对象的 preventDefault() 方法

html

<div id="app">
  <a href="http://www.qq.com" id="a">腾百万</a>
</div>
<script>
  document.getElementById("a").onclick = ev => {
    // 组织浏览器的默认行为
    ev.preventDefault();
  };
</script>

使用修饰符 阻止浏览器的默认行为

html

<div id="app">
  <a href="http://www.qq.com" @click.prevent="cli">腾百万</a>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    // 将事件处理程序写入methods对象
    methods: {
      cli: function() {
        alert("123");
      }
    }
  });
</script>

使用修饰符绑定一次性事件

html

<div id="app">
  <a href="http://www.qq.com" @click.once="cli($event)">腾百万</a>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    // 将事件处理程序写入methods对象
    methods: {
      cli: function(ev) {
        ev.preventDefault();
        alert("123");
      }
    }
  });
</script>

3.4.3 按键修饰符

绑定键盘抬起事件,但是只有enter 键能触发此事件

html

<div id="app">
  <input type="text" @keyup.enter="keyup" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      keyup: () => {
        console.log("111");
      }
    }
  });
</script>

3.4.4 系统修饰符

按住 shift 后才能触发点击事件

html

<div id="app">
  <input type="button" value="按钮" @click.shift="cli" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      cli: () => {
        console.log("111");
      }
    }
  });
</script>

3.4.5 鼠标修饰符

鼠标中键触发事件

html

<div id="app">
  <input type="button" value="按钮" @click.middle="cli" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      cli: () => {
        console.log("111");
      }
    }
  });
</script>

3.4.6 为什么在 HTML 中监听事件?

你可能注意到这种事件监听的方式违背了关注点分离 (separation of concern) 这个长期以来的优良传统。但不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。实际上,使用 v-on 有几个好处:

  1. 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
  2. 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
  3. 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。

3.5 v-show 显示隐藏

https://cn.vuejs.org/v2/api/#v-show

根据表达式之真假值,切换元素的 display CSS 属性。

html

<div id="app">
  <p v-show="is_show">Vue</p>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      is_show: false
    },
    methods: {}
  });
</script>

案例:点击按钮切换隐藏显示

html

<div id="app">
  <input type="button" value="按钮" @click="isshow" />
  <p v-show="is_show">Vue</p>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      is_show: false
    },
    methods: {
      isshow: function() {
        this.is_show = !this.is_show;
      }
    }
  });
</script>

3.6 v-if / v-else / v-else-if 条件判断

https://cn.vuejs.org/v2/api/#v-if

html

<div id="app">
  <div v-if="type === 'A'">
    A
  </div>
  <div v-else-if="type === 'B'">
    B
  </div>
  <div v-else-if="type === 'C'">
    C
  </div>
  <div v-else>
    Not A/B/C
  </div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      type: "F"
    }
  });
</script>

3.7 v-for 循环

https://cn.vuejs.org/v2/api/#v-for

html

<div id="app">
  <ul>
    <li v-for="(val,key) in arr">{ {val}}--- { {key}}< /li></li>
  </ul>

  <ul>
    <li v-for="(val,key) in obj">
      { {val}}--- { {key}}< /li> // in 也可以用 of 来替换 如: (val,key)of
      obj,两者没有区别
    </li>
  </ul>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      arr: ["a", "b", "c"],
      obj: { id: 1, name: "李四" }
    }
  });
</script>
//v-for 不只可以传入两个参数 ,可以传三个,顺序作用分别:(value 当前遍历的值,
键名name, 索引index)

3.8 v-cloak

https://cn.vuejs.org/v2/api/#v-cloak

和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

html

<div id="app">
  <p>{ {obj.i d}}</p>
</div>
<script src="./vue.js"></script>
<script>
  setTimeout(() => {
    var vm = new Vue({
      el: "#app",
      data: {
        arr: ["a", "b", "c"],
        obj: { id: 1, name: "李四" }
      }
    });
  }, 2000);
</script>

当我们的网络受阻时,或者页面加载完毕而没有初始化得到 vue 实例时,DOM 中的 { {}} 则会展示出来 ;

为了防止现象,我们可以使用 CSS 配合 v-cloak 实现获取 VUE 实例前的隐藏;

html

<style>
  [v-cloak] {
    display: none;
  }
</style>
<div id="app">
  <p v-cloak>{ {obj.i d}}</p>
</div>
<script src="./vue.js"></script>
<script>
  setTimeout(() => {
    var vm = new Vue({
      el: "#app",
      data: {
        obj: { id: 1, name: "李四" }
      }
    });
  }, 2000);
</script>

3.9 v-once

https://cn.vuejs.org/v2/api/#v-once

只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过

html

<div id="app">
  <p v-once>{ {msg}}< /p></p>
</div>

<script>
  var vm = new Vue({
    el: "#app",
    data: {
      msg: "kkk"
    }
  });
</script>

补充:数组更新检测/对象更新检测

https://cn.vuejs.org/v2/guide/list.html#%E6%95%B0%E7%BB%84%E6%9B%B4%E6%96%B0%E6%A3%80%E6%B5%8B

在 vue 的数据双向绑定中,数组以:arr[0] = value ,obj.v=1 等方式赋值或添加,都不会触发视图的更新,也就不能实现双向绑定,之所以会这样是因为在 Vue 每个数据都会进行包装/包囊,直接修改就会把包装给卸掉,但是也不是没有解决办法,解决这种情况可以使用以下几种方式:

数组监测

js

1.使用数组自带的添加、删除等等方法
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
2.改变引用\替换数组
如:
(1)使用一些会返回一个新数组的方法
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})
这样的方法有:filter()、concat() 和 slice()等
(2)直接重置赋值,在原有的基础上添加、删除等
如:   原数组attr = [1,2,3,4]
      attr = [1,2,3,4]
      这样也会改变引用
3.使用set\$set方法

对象监测

js

1.改变引用\重载对象
和数组同理,在这使用对象独有的 Object.assign 和jquery的$.extend
Object.assign(vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})
上面这种方式和直接obj.a = v一样,视图不会更新,要想发威作用必须以下面的方式
vm.userProfile = Object.assign({},vm.userProfile,{age:27,favoriteColor:'Vue Green'})
assign源对象不能直接是vue的数据,并且还要对vue指定的数据进行重置赋值
2.使用set/$set方法

set/$set 方法的使用

js

Vue.set(object 要添加等操作的数据, propertyName 键名, value 值)
Vue.$set 是vue的实例方法也是全局方法,使用方式和set一样

Vue.set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
vm.$set(vm.items, indexOfItem, newValue)

补充:is

https://cn.vuejs.org/v2/api/#is

https://cn.vuejs.org/v2/guide/components.html#%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6

is 命令的使用场景通常在必须使用固定的 DOM 子元素的 table、ul、select 等 DOM 元素上,解决组件在这些 DOM 中的使用发生错乱的问题,is 可以让 DOM 编译时改变成指定的组件

不受这种影响的情况有:

js

<!-- 当 `currentView` 改变时,组件也跟着改变 -->
<component v-bind:is="currentView"></component>

<!-- 这样做是有必要的,因为 `<my-row>` 放在一个 -->
<!-- `<table>` 内可能无效且被放置到外面 -->
<table>
  <tr is="my-row"></tr>
</table>

第 4 章 TodoList 案例

上市产品: ToDoList奇妙清单滴答清单

学习练手项目 : TodoMVCVue 官方示例

为什么选择这样的案例:

产品功能简洁,需求明确,所需知识点丰富;实现基本功能容易,涵盖所学基础知识;而可扩展性强,完善所有功能比较复杂,所需技术众多;在学习中,可以灵活取舍;

4.1 项目初始化

在项目目录中执行 npm install 命令,下载所需静态资源 ; 将 Vue.js 框架代码,复制到 js 目录,在 index.html 中引入 vue : <script src="./js/vue.js"></script>

同时 在 index.html 最下方,项目引入了 app.js ; 而我们要写的 vuejs 代码,都放在这个文件中;

4.2 数据遍历

js

const list_data = [
  { id: 1, title: "吃饭", stat: true },
  { id: 2, title: "睡觉", stat: false },
  { id: 3, title: "打豆豆", stat: true }
];

new Vue({
  el: "#todoapp",
  data: {
    // list_data:list_data,
    list_data // es6属性简写
  }
});

html

<ul class="todo-list">
  <li v-for="(val,key) in list_data">
    <div class="view">
      <input class="toggle" type="checkbox" v-model="val.stat" />
      <label>{ {val.titl e}}</label>
      <button class="destroy"></button>
    </div>
    <input class="edit" value="Rule the web" />
  </li>
</ul>

4.3 展示无数据状态

标签及内容都是在 section footer 两个标签中的,当 list_data 中没有数据时,我们只需要隐藏这个两个标签即可:

html

<section v-if="list_data.length" class="main">
  ……
</section>
<footer v-if="list_data.length" class="footer">
  ……
</footer>

两个标签都有 v-if 判断 ,因此我们可以使用一个 div 包裹两个标签,使 div 隐藏即可:

html

<div v-if="list_data.length">
  <section class="main">
    ……
  </section>
  <footer class="footer">
    ……
  </footer>
</div>

如果有内容,那么 DOM 书中就会多出一个 div 标签,那么我们可以选择使用 template (vue 中的模板标识),有内容时,浏览器渲染不会有此节点;

html

<template v-if="list_data.length">
  <section class="main">
    ……
  </section>
  <footer class="footer">
    ……
  </footer>
</template>

4.3 添加任务

绑定 enter 键盘事件:

html

<input @keyup.enter="addTodo" class="new-todo" placeholder="请输入" autofocus />

js

new Vue({
  el: "#todoapp",
  data: {
    // list_data:list_data,
    list_data // es6属性简写
  },
  //添加事件处理器
  methods: {
    // addTodo:function(){}
    // 简写形式
    addTodo() {
      console.log(123);
    }
  }
});

修改代码完成任务添加:

js

methods: {
    // 添加任务
    // addTodo:function(){}
    // 简写形式
    addTodo(ev) {
        // 获取当前触发事件的元素
        var inputs = ev.target;
        // 获取value值,去除空白后判断,如果为空,则不添加任务
        if (inputs.value.trim() == '') {
            return;
        }
        // 组装任务数据
        var todo_data = {
            id: this.list_data.length + 1 + 1,
            title: inputs.value,
            stat: false
        };
        // 将数据添加进数组
        this.list_data.push(todo_data);
        // 清空文本框内容
        inputs.value = '';
    }
}

4.4 任务的全选与反选

点击文本框左边的下箭头,实现全选和反选操作

为元素绑定点击事件:

html

<input @click="toggleAll" id="toggle-all" class="toggle-all" type="checkbox" />

添加处理程序:

js

toggleAll(ev){
   // 获取点击的元素
    var inputs = ev.target;
    // console.log(inputs.checked);
    // 循环所有数据为状态重新赋值
    // 因为每个元素的选中状态都是使用 v-model 的双向数据绑定,
    // 因此 数据发生改变,状态即改变,状态改变,数据也会改变
    for(let i=0;i<this.list_data.length;i++){
        this.list_data[i].stat = inputs.checked;
    }
}

4.5 完成任务

如果任务完成,状态改为选中, liclass 属性为 completed 时文字有中划线;

html

<li v-for="(val,key) in list_data" v-bind:class="{completed:val.stat}"></li>

4.6 删除任务

绑定点击事件,将当前索引值传入事件处理程序:

html

<button @click="removeTodo(key)" class="destroy"></button>

按照索引,删除相应的数据:

js

removeTodo(key){
    this.list_data.splice(key,1);
},

4.7 删除已完成的任务

绑定事件

html

<button @click="removeAllDone" class="clear-completed">Clear completed</button>

循环遍历所有数据,删除已被标记为完成的任务:

js

removeAllDone(){
    for(let i=0;i<list_data.length;i++){
        if(list_data[i].stat == true){
            this.list_data.splice(i,1);
        }
    }
}

循环的代码看起来很不舒服, Array.prototype.filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

js

var arr = [1, 4, 6, 2, 78, 23, 7, 3, 8];
// 原始写法
// var new_arr = arr.filter(function(v){
//     // if(v>8){
//     //     return true;
//     // }
//     return v>8;
// })

// 箭头函数写法
// var new_arr = arr.filter((v)=>{
//     return v>8;
// })

// 精简写法
var new_arr = arr.filter(v => v > 8);

console.log(new_arr);

修改项目代码:

js

removeAllDone(){
    // 原始循环判断用法
    // for(let i=0;i<list_data.length;i++){
    //     if(list_data[i].stat == true){
    //         this.list_data.splice(i,1);
    //     }
    // }

    // 上面是循环删除符合条件的数据
    // 下面是保留不符合条件的数据

    // 原始标准库对象方法
    // this.list_data = this.list_data.filter(function(v){
    //     if(v.stat == false){
    //         return true;
    //     }
    // })

    // 箭头函数方法
    // this.list_data = this.list_data.filter(function(v){
    //     return !v.stat;
    // })

    // 精简方法
    this.list_data = this.list_data.filter((v)=>!v.stat);
},

TodoList 案例暂时告一段落,我们并没有将产品做完,因为我们需要用到其他知识了;

Vue Devtools 调试工具 在使用 Vue 时,我们推荐在你的浏览器上安装 Vue Devtools。它允许你在一个更友好的界面中审查和调试 Vue 应用。

第 5 章 MVVM 设计思想

MVC 设计思想:

M: model 数据模型层 提供数据

V: Views 视图层 渲染数据

C: controller 控制层 调用数据渲染视图

MVVM 设计思想:

M: model 数据模型层 提供数据

V: Views 视图层 渲染数据

VM:ViewsModel 视图模型层 调用数据渲染视图

​ 由数据来驱动视图(不需要过多考虑 dom 操作,把重心放在 VM)

第 6 章 其他知识点汇总

6.1 计算属性与侦听器

6.1.1 计算属性

html

<div id="div">
  <input type="text" v-model="xing" />
  <input type="text" v-model="ming" />
  { {xing + ming}}
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      xing: "",
      ming: ""
    }
  });
</script>

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。因此我们可以使用方法,来进行运算并返回数据:

html

<div id="div">
  <input type="text" v-model="xing" />
  <input type="text" v-model="ming" />
  { { fullnam e() }}
  <!-- 一百次调用,观察时间结果-->
  { { fullnam e() }}
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      xing: "",
      ming: ""
    },
    methods: {
      fullname() {
        return this.xing + this.ming + Date.now();
      }
    }
  });
</script>

注意,每次在模板中使用 { { fullnam e() }} fullname 方法就会被调用执行一次;所以,对于任何复杂逻辑,你都应当使用计算属性 ,因为计算属性,会自动缓存数据:

html

<div id="div">
  <input type="text" v-model="xing" />
  <input type="text" v-model="ming" />
  <br />
  { {fulln} }
  <!-- 一百次调用 -->
  { {fulln} }
</div>

<script>
  var app = new Vue({
    el: "#div",
    data: {
      xing: "",
      ming: ""
    },
    computed: {
      fulln() {
        return this.xing + this.ming + Date.now();
      }
    }
  });
</script>

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值;多次调用,计算属性会立即返回之前的计算结果,而不必再次执行函数。

6.1.2 利用计算属性获取未完成任务个数

html

<span class="todo-count"><strong>{ {getNu}}< /strong> item left</span>

js

computed: {
    // 未完成任务个数
    getNu() {
        return (this.list_data.filter((v) => !v.stat)).length;
    }
}

6.1.3 使用侦听器

html

<div id="div">
  <input type="text" v-model="xing" />
  <input type="text" v-model="ming" />
  { { fullnam e }}
</div>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      xing: "",
      ming: "",
      fullname: ""
    },
    // 设置侦听器
    watch: {
      // 侦听器中的方法名和要真挺的数据属性名必须一致
      // xing 发生变化,侦听器就会被执行,且将变化后的值和变化前的值传入
      xing: function(newVal, old_val) {
        this.fullname = newVal + this.ming;
      },
      ming: function(newVal, oldVal) {
        this.fullname = this.xing + newVal;
      }
    }
  });
</script>

通过上面的案例,我们基本掌握了侦听器的使用,但是我们也发现,与计算属性相比,侦听器并没有优势;也不见得好用,直观上反而比计算属性的使用更繁琐;

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

html

<div id="div">
  <input type="text" v-model="xing" />
  <input type="text" v-model="ming" />
  { { fullnam e }}
</div>
<script src="./jq.js"></script>
<script>
  var app = new Vue({
    el: "#div",
    data: {
      xing: "",
      ming: "",
      fullname: ""
    },
    // 设置侦听器
    watch: {
      // 侦听器中的方法名和要真挺的数据属性名必须一致
      // xing 发生变化,侦听器就会被执行,且将变化后的值和变化前的值传入
      xing: function(newVal, old_val) {
        // this.fullname = newVal+this.ming;
        var t = this;
        // 在侦听器中执行异步网络请求
        $.get("./xx.php", d => {
          t.fullname = d;
        });
      }
    }
  });
</script>

6.2 使用 ref 操作 DOM

在学习 jq 时,我们首要任务就是学习选择的使用,因为选择可以极其方便帮助我们获取节点查找 dom,因为我们要通过 dom 展示处理数据。而在 Vue 中,我们的编程理念发生了变化,变为了数据驱动 dom;但有时我们因为某些情况不得不脱离数据操作 dom,因此 vue 为我们提供了 ref 属性获取 dom 节点;

html

<div id="app">
  <input type="button" @click="click" value="按钮" /> <br />
  <p ref="pv">123</p>
</div>
<script>
  var app = new Vue({
    el: "#app",
    methods: {
      click: function() {
        // 使用原生JS获取dom数据
        // var p = document.getElementsByTagName('p')[0].innerHTML;
        // console.log(p);

        // 使用vue ref 属性获取dom数据
        var d = this.$refs.pv.innerHTML;
        console.log(d);
      }
    }
  });
  console.log(app.$refs);
</script>

但是在项目开发中,尽可能不要这样做,因为从一定程度上,ref 违背的 mvvm 设计原则;

6.3 过滤器的使用

6.3.1 私有(局部)过滤器

定义过滤器

js

var app = new Vue({
  el: "#app",
  data: { msg: "UP" },
  //定义过滤器
  filters: {
    // 过滤器的名称及方法
    myFilters: function(val) {
      return val.toLowerCase();
    }
  }
});

过滤器的使用:

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化转义等操作。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器要被添加到操作值得后面,使用 管道符 | 分割;vue 会自动将操作值,以实参的形式传入过滤器的方法中;

{ {msg|myFilter s}}

过滤敏感词汇

html

<div id="app">
  <input type="text" v-model="msg" /> <br />
  { {msg|myFilter s|get3}}
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      msg: ""
    },
    //定义过滤器
    filters: {
      // 过滤器的名称及方法
      myFilters: function(val) {
        return val.toLowerCase();
      },
      get3: function(val) {
        // 遇到数字替换为 0
        // var reg = /\d/g;
        // return val.replace(reg,0);

        return val.replace("苍井空", "***");
      }
    }
  });
</script>

6.3.2 全局过滤器

上面的代码中,myFiltersget3 两个过滤器,仅在当前 vue 实例中可用;如果在代码 再次 var app2 = new Vue() 得到变量为 app2 的 vue 实例,则两个过滤器在 app2 中都不可用;如果需要过滤器在所有实例对象中可用,我们需要声明 全局过滤器

Vue.filter(名称,处理器)

html

<div id="app">
  <input type="text" v-model="msg" /> <br />
  { {msg|myFilter s}}
</div>
<!-- 定义两个DOM节点 -->
<div id="app2">
  <input type="text" v-model="msg" /> <br />
  { {msg|myFilter s|get3}}
</div>
<script>
  Vue.filter("myFilters", function(val) {
    return val.toLowerCase();
  });
  // 定义两个全局过滤器
  Vue.filter("get3", function(val) {
    return val.replace("苍井空", "***");
  });

  // 两个Vue 实例
  var app = new Vue({
    el: "#app",
    data: {
      msg: ""
    }
  });
  var app2 = new Vue({
    el: "#app2",
    data: {
      msg: ""
    }
  });
</script>

6.4 自定义指令

前面我们学过 v-on 、v-model、v-show 等指令,在操作 dom 时使用了 ref 属性,其实之前学过的指令也是操作 dom 的一种方式,但有时,这些指令并不能满足我们的需求,因此 vue 允许我们自定义指令来操作 dom

6.4.1 全局自定义指令

js

<div id="app">
    <p v-setcolor>自定义指令的使用</p>
</div>
<script>
    // 注册一个全局自定义指令 `v-focus`
    Vue.directive('setcolor', {
        // 当被绑定的元素插入到 DOM 中时……
        inserted: function (el) {
            // 聚焦元素
            el.style.color = 'red';
        }
    })
    var app = new Vue({
        el: '#app',
    })
</script>

6.4.2 私有(局部)自定义指令

html

<div id="app">
  <p v-setcolor>自定义指令的使用</p>
</div>
<script>
  var app = new Vue({
    el: "#app",
    // 注册 局部(私有)指令
    directives: {
      // 定义指令名称
      setcolor: {
        // 当被绑定的元素插入到 DOM 中时……
        inserted: function(el) {
          // 聚焦元素
          el.style.color = "red";
        }
      }
    }
  });
</script>

6.4.3 利用自定义指令使 TodoList 获取焦点

html

<input
  @keyup.enter="addTodo"
  v-getfocus
  class="new-todo"
  placeholder="请输入"
/>

js

// 注册 局部(私有)指令
directives: {
    // 定义指令名称
    getfocus: {
        // 当被绑定的元素插入到 DOM 中时……
        inserted: function (el) {
            // 聚焦元素
            el.focus()
        }
    }
},

6.4.4 为自定义指令传值

之前学习的指令中,有的指令可以传值,有的则没有,而我们自定的指令中是没有值的,如果想为自定义指令赋值,如下即可:

html

<div id="app">
  <p v-setcolor="colors">自定义指令的使用</p>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      colors: "yellow"
    },
    // 注册 局部(私有)指令
    directives: {
      // 定义指令名称
      setcolor: {
        // 自定义指令可以接受第二个参数
        inserted: function(el, val) {
          // 第二个参数中包含了指令名称、挂载名称及数据键值
          console.log(val);
          // 聚焦元素
          el.style.color = val.value;
        }
      }
    }
  });
</script>

6.5 过度及动画

我们可以使用 v-if 或者 v-show 控制 dom 元素的显示和隐藏

html

<div id="app">
  <button @click="go">显示/隐藏</button>
  <p v-show="is">pppppp1111</p>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      isShow: true
    },
    methods: {
      go() {
        this.isShow = !this.isShow;
      }
    }
  });
</script>

而在显示和隐藏的过程中,我们加入一些动画效果:

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to: 2.1.8 版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to: 2.1.8 版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter

html

<style>
  .fade-enter-active,
  .fade-leave-active {
    transition: opacity 1s;
  }

  .fade-enter,
  .fade-leave-to {
    opacity: 0;
  }

  .man-enter-active,
  .man-leave-active {
    transition: opacity 4s;
  }

  .man-enter,
  .man-leave-to {
    opacity: 0;
  }
</style>

<div id="app">
  <button @click="go">显示/隐藏</button>
  <transition name="fade">
    <p v-show="isShow">pppppp1111</p>
  </transition>

  <transition name="man">
    <p v-show="isShow">pppppp222</p>
  </transition>

  <transition-group name="fade" tag="ul">
    <li v-for="(v,k) in list" :key="v">
      { {v} }
      <a href="javascript:void(0)" @click="del(k)">删除</a>
    </li>
  </transition-group>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      isShow: true,
      list: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    },
    methods: {
      go() {
        this.isShow = !this.isShow;
      },
      del(index) {
        this.list.splice(index, 1);
      }
    }
  });
</script>
<!--

注意:transition只对单个有效,而transition-group可以对一组有效,两者使用方式基本一样,后者有tag,这个属性作用主要是当页面完成后,替换transition标签,可以是任何DOM元素

可以配合第三方vue2-animate包使用
npm install vue2-animate
此包只需在transition标签的name加上要应用的效果即可(以上示例)

也可使用原始的animate官方包 ,效果更多,但需自行修改class名称
下载:从下面效果展示链接下载
以下是原animate使用示例
-->
<transition
  name:fade
  enter-active-class="animated swing"
  leave-active-class="animated shake"
>
</transition>
<!--使用注意:在修改类内必须加animate 后面接着要使用的动画类即可-->

transition

  • name - string,用于自动生成 CSS 过渡类名。例如:name: 'fade' 将自动拓展为.fade-enter.fade-enter-active等。默认类名为 "v"
  • appear - boolean,是否在初始渲染时使用过渡。默认为 false
  • css - boolean,是否使用 CSS 过渡类。默认为 true。如果设置为 false,将只通过组件事件触发注册的 JavaScript 钩子。
  • type - string,指定过渡事件类型,侦听过渡何时结束。有效值为 "transition""animation"。默认 Vue.js 将自动检测出持续时间长的为过渡事件类型。
  • mode - string,控制离开/进入的过渡时间序列。有效的模式有 "out-in""in-out";默认同时生效。
  • duration - number | { enter: number, leave: number } 指定过渡的持续时间。默认情况下,Vue 会等待过渡所在根元素的第一个 transitionendanimationend 事件。

transition-group

  • tag - string,默认为 span
  • move-class - 覆盖移动过渡期间应用的 CSS 类。
  • 除了 mode,其他特性和 <transition> 相同。

初次渲染动画

js

//在transition标签中,可以设置一个appear指令,这个指令可以实现打开网页\初次进入时触发动画
//基本使用:
<transition appear ></transition>
//也可使用自定义动画效果或第三方库,通过修改class实现
<transition appear  appear-active-class='animated swing' ></transition>
//appear也可修改类名

vue2-animate 动画样式参考链接:https://the-allstars.com/vue2-animate/

animate 样式参考链接:https://daneden.github.io/animate.css/

官方文档所说:对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter

这就是 Vue 中动画及过渡的基本使用方式,因为这些动画效果都需要我们自己写 CSS 样式,相对比较麻烦,在项目中,大多情况下,我们会借助第三方 CSS 动画库来实现,如:Animate.css ;后面项目中具体使用时,我们在进一步学习第三方 CSS 动画库的使用;

使用 js 钩子来动画

html

<transition <!-- 进入 -->
  v-on:before-enter="beforeEnter" v-on:enter="enter"
  v-on:after-enter="afterEnter" v-on:enter-cancelled="enterCancelled"
  <!-- 离开 -->
  v-on:before-leave="beforeLeave" v-on:leave="leave"
  v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" >
  <!-- ... -->
</transition>

// ... methods: { // -------- // 进入中 // -------- beforeEnter: function (el) {
// ... }, // 当与 CSS 结合使用时 // 回调函数 done 是可选的 enter: function (el,
done) { // ... done() }, afterEnter: function (el) { // ... }, enterCancelled:
function (el) { // ... }, // -------- // 离开时 // -------- beforeLeave:
function (el) { // ... }, // 当与 CSS 结合使用时 // 回调函数 done 是可选的
leave: function (el, done) { // ... done() }, afterLeave: function (el) { // ...
}, // leaveCancelled 只用于 v-show 中 leaveCancelled: function (el) { // ... } }

<!--
@before-enter   动画执行前
@enter        执行动画
@after        动画执行中
@enter        执行完毕
//离开/进入 各有一套
-->

当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。

推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

Velocity.js + js 钩子

velocity 下载链接:http://www.velocityjs.org/

https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90

html

<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
<script>
  new Vue({
    el: "#example-4",
    data: {
      show: false
    },
    methods: {
      beforeEnter: function(el) {
        el.style.opacity = 0;
        el.style.transformOrigin = "left";
      },
      //进入
      enter: function(el, done) {
        Velocity(el, { opacity: 1, fontSize: "1.4em" }, { duration: 300 });
        Velocity(el, { fontSize: "1em" }, { complete: done }); //complete 作用:通知动画结束
      },
      //离开
      leave: function(el, done) {
        Velocity(
          el,
          { translateX: "15px", rotateZ: "50deg" },
          { duration: 600 }
        );
        Velocity(el, { rotateZ: "100deg" }, { loop: 2 });
        Velocity(
          el,
          {
            rotateZ: "45deg",
            translateY: "30px",
            translateX: "30px",
            opacity: 0
          },
          { complete: done }
        );
      }
    }
  });
</script>

多个元素/组件过渡

https://cn.vuejs.org/v2/guide/transitions.html#%E5%A4%9A%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84%E8%BF%87%E6%B8%A1

html

<!--多个元素-->
<transition mode='out-in'> <!--如果不设置mode 那么开始离开动画将同时执行,有时效果会不那么好,
  参数可以参考以上,这里用的是先离开后进入 -->
    <button v-if='show === 'div1' ' key='1'>  <!--相同标签名必须设置key区分,否则应用全部-->
        name:1
    </button>
    <button v-if='show === 'div2' ' key='2'>
        name:2
    </button>

</transition>
<button @click='toggle'>
        toggle
    </button>
new Vue({
el:'#app',
data:{
show:'div1'
},
methods:{
toggle()
    {
        this.show = this.show === 'div1' ? 'div2' : 'div1'
    }
}
})
<!--上面的例子利用动态key简写-->
<transition :key='show' mode='out-in'>
    { {ShowMassage} }
</transition>
<button @click='toggle'>
    toggle
</button>
new Vue({
el:'#app',
data:{
show:'div1'
},
computed:{
    ShowMassage()
    {
         switch (this.show) {
          case 'div1': return 'div1'
          case 'div2': return 'div2'
        }
    }
 },
methods:{
toggle()
    {
        this.show = this.show === 'div1' ? 'div2' : 'div1'
    }
}
})
<!--在一些场景中,也可以通过给同一个元素的 key 特性设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为:-->
<transition>
  <button v-bind:key="isEditing">
    { { isEditin g ? 'Save' : 'Edit' }}
  </button>
</transition>
<!--多个组件-->
<!--不需要使用 key 特性。相反,我们只需要使用动态组件-->
<transition  mode="out-in">
  <component :is="view"></component>
</transition>
<button @click='toggle'>
    toggle
</button>
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
methods:{
toggle()
{
this.view = this.view === 'v-a' ? 'v-b' :'v-a'
}
},
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})

第 7 章 json-server 与 axios

一个项目从立项开始,一般都是前后端同时进行编码工作的,而此时前端需要的接口和数据后台都是无法提供的;

7.1 json-server 使用

使用全局安装 :npm install json-server -g

json-server 会将一个 json 文件作为数据库来存储数据,对 json 数据的格式是有要求的,如 data.json 的内容:

json

{
  "tb1": [
    {
      "id": 1,
      "title": "标题1",
      "author": "描述信息1"
    },
    {
      "id": 2,
      "title": "标题2",
      "author": "描述信息2"
    }
  ],
  "tb2": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    }
  ],
  "tb3": {
    "name": "typicode"
  }
}

启动服务: json-server --watch data.json

启动成功后,提示信息如下:

shell

$ json-server --watch data.json

  \{^_^}/ hi!

  Loading data.json
  Done

  Resources
  http://localhost:3000/tb1
  http://localhost:3000/tb2
  http://localhost:3000/tb3

  Home
  http://localhost:3000

  Type s + enter at any time to create a snapshot of the database
  Watching...

得到 tb1 所有的数据 GET: http://localhost:3000/tb1

根据 id 得到数据 GET : http://localhost:3000/tb1/2

添加一条数据 POST: http://localhost:3000/tb1

删除一条数据 DELETE: http://localhost:3000/tb1/2

模糊查找 GET : http://localhost:3000/tb1?title_like=标题

根据 id 修改数据 PUT: http://localhost:3000/tb1/1

注意:json-server 严格遵循 HTTP 请求语义进行数据处理

7.2 axios

我们在构建应用时需要访问一个 API 并展示其数据。做这件事的方法有好几种,而使用基于 Promise 的 HTTP 客户端 axios 则是其中非常流行的一种。

html

<script src="./axios.js"></script>
<script>
  // 获取全部数据
  axios.get("http://localhost:3000/list_data").then(data => {
    console.log(data);
  });

  // 获取一条数据
  axios.get("http://localhost:3000/list_data/2").then(data => {
    console.log(data);
  });

  // 添加一条数据
  axios
    .post("http://localhost:3000/list_data", { stat: false, title: "喝水" })
    .then(d => {
      console.log(d);
    })
    .catch(error => console.log(error));

  // 删除一条数据
  axios
    .delete("http://localhost:3000/list_data/4")
    .then(d => {
      console.log(d);
    })
    .catch(error => console.log(error));

  // 修改一条数据
  axios
    .put("http://localhost:3000/list_data/6", { title: "hhhhhh" })
    .then(d => {
      console.log(d);
    })
    .catch(error => console.log(error));
</script>

第 8 章 重构 TodoList 案例

8.1 启动 API 接口及数据

db.json:

json

{
  "list_data": [
    {
      "id": 1,
      "title": "吃饭",
      "stat": true
    },
    {
      "id": 2,
      "title": "睡觉",
      "stat": false
    },
    {
      "id": 3,
      "title": "打豆豆",
      "stat": true
    }
  ]
}

启动服务: json-server --watch db.json

8.2 获取全部任务

js

el: '#todoapp',
data: {
    // list_data:list_data,
    list_data:[]// es6属性简写
},

// 当vue实例获取到 el:'#todoapp' 自动调用执行 mounted 方法
mounted:function(){
    let url = 'http://localhost:3000/list_data';
    axios.get(url).then((backdata)=>{
        // console.log(backdata.data);
        this.list_data = backdata.data;
    })
},

8.3 添加任务

js

……
methods: {
    // 添加任务事件处理器
    // addTodo:function(){}
    // 简写形式
    addTodo(ev) {
        // 获取当前触发事件的元素
        var inputs = ev.target;
        // 获取value值,去除空白后判断,如果为空,则不添加任务
        if (inputs.value.trim() == '') {
            return;
        }
        // 组装任务数据
        var todo_data = {
            // 通过服务器添加数据时,不需要id值
            // id: this.list_data.length + 1 + 1,
            title: inputs.value,
            stat: false
        };
        let url = 'http://localhost:3000/list_data';
        // 将数据提交保存到服务器
        axios.post(url,todo_data).then((back_data)=>{
            let {data,status} = back_data;
            if(status == 201){
                // console.log(this.list_data);
                // 数据保存成功后,将数据添加到任务列表展示
                this.list_data.push(data);
            }
        })
        // 清空文本框
        inputs.value = '';
    },

    ……

8.4 删除任务

html

<button @click="removeTodo(key,val.id)" class="destroy"></button>

js

// 删除操作
removeTodo(key,id) {
    let url = 'http://localhost:3000/list_data/'+id;
    axios.delete(url).then((back_data)=>{
        // 结构对象
        let {data,status} = back_data;
        // console.log(back_data);
        if(status == 200){
            this.list_data.splice(key, 1);
        }
    })
},

8.5 完成任务

html

<li
  v-for="(val,key) in list_data"
  @click="todoDone(key,val.id)"
  v-bind:class="{completed:val.stat}"
></li>

js

// 完成任务 事件处理器(新添加,原案例中没有)
todoDone(key,id){
    let url = 'http://localhost:3000/list_data/'+id;
    // 组装数据准备修改服务器数据
    setdata = {};
    // 注意:事件优先于浏览器渲染执行,获取当前状态
    var chestat = this.list_data[key].stat;
    // 状态取反
    setdata.stat = !chestat;
    setdata.title = this.list_data[key].title;
    // console.log(setdata);
    axios.put(url,setdata).then((backdata)=>{
        var {data,status} = backdata;
        // 如果服务器修改失败,则重新渲染DOM节点样式,改回原始状态
        // 服务器返回状态有误
        if(status != 200){
            this.list_data[key].stat = chestat;
        }
        // 如果异步执行失败失败,则重新渲染DOM节点样式,改回原始状态
    }).catch((err)=>{
        if(err){
            this.list_data[key].stat = chestat;
        }
    })
},

8.6 案例中的 Bug

修改:<button @click.stop="removeTodo(key,val.id)" class="destroy"></button>

第 9 章 组件

https://cn.vuejs.org/v2/guide/components.html

https://cn.vuejs.org/v2/guide/components-registration.html

9.1 认识组件

组件系统是 Vue 的一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。通常一个应用会以一棵嵌套的组件树的形式来组织:

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

9.2 基本使用

组件是可复用的 Vue 实例,且带有一个名字。把这个组件作为自定义元素来使用。组件的好处是写一次可以进行任意次数的复用。

html

//1.
<div id="app">
  <!-- 使用组件 -->
  <!-- 将组件名直接当做标签名在html代码中使用即可 -->
  <mytemp></mytemp>
  <!-- 组件可以进行任意次数的复用 -->
  <mytemp></mytemp>
</div>
//2.
<template id="app2">
  <h2>我是一个组件</h2>
</template>

<script>
  // 定义一个名为 mytemp 的新组件
  Vue.component("mytemp", {
    // template属性的值,作为组件的内容
    // vue 会把这个值替换到html中并会被浏览器渲染
    template: "<h2>我是一个组件</h2>" //template 值也可以是  #app2 像vue实例的el一样
  });
  var app = new Vue({
    el: "#app"
  });
</script>

上面代码中我们直接使用 Vue.component() 方法定义了组件,而这个 mytemp 组件可以用在所有 vue 实例中,

这种组件被称为 全局组件

在具体的某个 vue 实例中,也可以定义组件,但是组件仅会在具体的 vue 实例中起作用,这种组件被称为 局部(私有)组件

html

<div id="app">
  <!-- 使用组件 -->
  <!-- 将组件名直接当做标签名在html代码中使用即可 -->
  <mytemp></mytemp>
</div>
<div id="app2">
  <!-- 不可用 -->
  <mytemp></mytemp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    // app 的私有组件,其他实例对象不可用
    components: {
      mytemp: {
        template: "<h2>我是一个组件</h2>"
      }
    }
  });
  var app2 = new Vue({
    el: "#app2"
  });
</script>

9.3 使用注意

组件名如果是驼峰法命名,使用组件时要将大写字母改为小写,并且在前面加上 -

组件中的 tamplate 属性必须有一个唯一的根元素,否则会报错

html

<div id="app">
  <!-- 使用组件 -->
  <!-- 将组件名直接当做标签名在html代码中使用即可 -->
  <my-temp></my-temp>
  <!-- 单标签方式使用 -->
  <my-temp />
</div>
<div id="app2">
  <!-- 不可用 -->
  <mytemp></mytemp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    // app 的私有组件,其他实例对象不可用
    components: {
      // 驼峰法命名
      myTemp: {
        // 必须有唯一的根标签,多标签报错
        template: "<div><h2>我是一个组件</h2><h3>df</h3></div>"
      }
    }
  });
  var app2 = new Vue({
    el: "#app2"
  });
</script>

9.4 组件的使用

CSS 代码

css

* {
  margin: 0;
  padding: 0;
}

.top {
  width: 100%;
  height: 80px;
  background-color: #ccc;
}

.left {
  margin-top: 20px;
  width: 800px;
  height: 600px;
  background-color: #ccc;
  float: left;
}

.right {
  margin-top: 20px;
  width: 400px;
  height: 600px;
  background-color: #ccc;
  float: right;
}

原始 HTML 代码

html

<div id="app">
  <div class="top">我是顶</div>
  <div class="left">我是左</div>
  <div class="right">我是右</div>
</div>

组件化代码

html

<div id="app">
  <tops></tops>
  <lefts></lefts>
  <rights></rights>
</div>
<script>
  var app = new Vue({
    el: "#app",
    components: {
      tops: {
        template: '<div class="top">我是顶</div>'
      },
      lefts: {
        template: '<div class="left">我是左</div>'
      },
      rights: {
        template: '<div class="right">我是右</div>'
      }
    }
  });
</script>

9.5 组件中的数据及方法

组件是带有名字的可复用的 Vue 实例 ,所以它们与 new Vue 实例对象接收相同的参数选项 datacomputedwatchmethods , 但 el例外;

虽然组件和实例对象可以接收相同的参数选项,但在具体使用中,vue 实例对象的 data 与组件中的 data 还是有差异的, 在我们自己写的组件中,data 必须是一个函数

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回的对象;

html

<div id="app">
  <my-temp></my-temp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    components: {
      myTemp: {
        // 一个组件的 data 选项必须是一个函数
        data: function() {
          // 将 数据 装入 对象 返回
          return { msg: "我是data选项" };
        },
        // 其他选项的使用不受影响
        methods: {
          cli() {
            alert(123);
          }
        },
        template: "<div @click='cli'>{ {msg}}< /div>"
      }
    }
  });
</script>

data 选项外,其他选项的使用都是一样的;

9.6 vue 实例也是组件

通过new Vue() 可以得到一个实例对象,其实这个实例对象就是一个特殊的组件,也有 template 参数,也可以当做组件来使用;

html

<div id="app">
  { {msg} }
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: { msg: "数据" },
    template: "<h2>组件</h2>"
  });
</script>

上面的代码中直接为 Vue 实例对象传入了 template 参数,那么 vue 会使用template中的数据替换 el 选中的整个 DOM 节点 , 因此 data 选项中的的数据也不会绑定,因为在绑定数据之前,整个 DOM 节点包括节点中 { {msg}} 都会被替换;如果想让数据正常绑定,我们可以在 template 数据中加入 { {msg}}

html

<div id="app">
  { {msg} }
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: { msg: "数据" },
    template: "<h2>组件{ {msg}}< /h2>"
  });
</script>

父子组件通信*

通过 Prop 向子组件传递数据

https://cn.vuejs.org/v2/guide/components.html

html

<div id="app">
  <mytemp></mytemp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: { msg: "数据" },
    components: {
      mytemp: {
        template: "<h2>data:{ {msg}}< /h2>"
      }
    }
  });
</script>

运行上面的代码,我们发现,组件 mytemp 并不能获取实例中 data 的数据,这是因为组件与组件之间都拥有各自独立的作用域;

vue 在组件中提供了props 选项,props 接受一个在组件中自定义属性的值;

html

<div id="app">
  <mytemp cc="我是cc"></mytemp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: { msg: "数据" },
    components: {
      mytemp: {
        template: "<h2>data:{ {cc}}< /h2>",
        props: ["cc"]
      }
    }
  });
</script>

我们知道了 props 的用法后,怎么才能将 vue 实例对象中的数据传入组件中呢?我们可以借助 v-bind 指令来进行传值;

html

<div id="app">
  <mytemp v-bind:cc="msg" v-bind:kk="msg2"></mytemp>
</div>
<script>
  var app = new Vue({
    el: "#app",
    data: {
      msg: "数据",
      msg2: "数据二"
    },
    components: {
      mytemp: {
        template: "<h2>data:{ {cc}} <br>{ {kk}}< /h2>",
        props: ["cc", "kk"]
      }
    }
  });
</script>

vue 实例对象也是一个组件,而 mytemp 组件就是运行在 实例对象下面的,这时我们也会将 实例对象称为 父组件,将 mytemp 组件称为 子组件; 而我们上面的代码,实际上已经实现了 父组件向子组件传递数据的 功能;

检索 prop 数据类型

js

//Prop 类型
//到这里,我们只看到了以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
//但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型:

props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

通过$ref 实现父传子

对于 ref 官方的解释是:ref 是被用来给元素或子组件注册引用信息的。引用信息将会注册在父组件的 $refs 对象上。 看不懂对吧?很正常,我也看不懂。那应该怎么理解?看看我的解释:

  • 如果 ref 用在子组件上,指向的是组件实例,可以理解为对子组件的索引,通过$ref 可能获取到在子组件里定义的属性和方法
  • 如果 ref 在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,通过$ref 可能获取到该 DOM 的属性集合,轻松访问到 DOM 元素,作用与 JQ 选择器类似。

那如何通过$ref 实现通信?下面我将上面prop实现的功能,用$ref 实现一遍

js

 <!-- 父组件 -->
    <div id="app">
      <h1>我是父组件!</h1>
      <child ref="msg"></child>
    </div>
    <!-- 子组件 -->
    <template id="app2">
      <div>
        <h1>我是子组件一!</h1>
      </div>
    </template>
    <script>
      let app = new Vue({
        el: "#app",
        components: {
          Child: {
            template: "#app2",
            data() {
              return {
                msg: "子组件的信息"
              };
            },
            methods: {
              getmsg() {
                return this.msg;
              }
            }
          }
        },
        mounted: function() {
        //   console.log(this.$refs.msg); 子组件的属性和方法都可以在$refs中拿到
          this.msg = this.$refs.msg.getmsg();
          console.log("父组件:"+this.msg+'已经被我拿到了');
        }
      });
    </script>

prop 着重于数据的传递,它并不能调用子组件里的属性和方法。像创建文章组件时,自定义标题和内容这样的使用场景,最适合使用 prop。

$ref 着重于索引,主要用来调用子组件里的属性和方法,其实并不擅长数据传递。而且 ref 用在 dom 元素的时候,能使到选择器的作用,这个功能比作为索引更常有用到。

通过自定义事件实现子向父传递数据

js

<div id="app" >
        //3.在父组件中绑定传过来的自定义事件,然后使用这个自定义事件绑定自己的函数,即可实现子传父
        <mytemp @childevents="Sendparent"> </mytemp>
        //<mytemp @childevents="msg = $event"> </mytemp> 也可以不用函数,使用$event来获取发来的值
</div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          msg: "数据"
        },
        components: {
          mytemp: {
               //1.在子组件模板中,定义一个触发事件,触发的函数必须是子组件自己拥有的函数
            template: ' <input type="button" value="提交" @click="send" />',
            data() {
              return {
                chilAttr: "child"
              };
            },
            methods: {
              send() {
                //2.使用$emit实现子传父
                this.$emit("childevents", this.chilAttr);
                //向父元素发送自定义事件    两个参数:1 自定义事件名   2 传参
              }
            }
          }
        },
        methods: {
          Sendparent(child) {
              this.msg = child;
          }
        }
      });
    </script>
//注意: 自定义事件的命名不能为驼峰,否则会出错。

父子之间访问

vm.$parent
  • 类型Vue instance
  • 只读
  • 详细: 父实例,如果当前实例有的话。
vm.$root
  • 类型Vue instance
  • 只读
  • 详细: 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
vm.$children
  • 类型Array<Vue instance>
  • 只读
  • 详细: 当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

非父子组件传值

有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线。原理就是把 Vue 实例当作一个中转站。

可以像上图一样,把 vue 实例放在 vue 的原型上,也可想下面一样,放在根 Vue 的 data 中,使用$root 访问

html

<div id="app">
  <child content="hello"></child>
  <child content="world"></child>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script>
  //子组件
  let child = {
    props: {
      content: String
    },
    data: function() {
      return {
        childCt: this.content
      };
    },
    template: "<button @click='add'>{ {childCt}}< /button>",
    methods: {
      add() {
        // $root 可以访问到根组件
        this.$root.Bus.$emit("change", this.childCt);
      }
    },
    mounted() {
      let that = this;
      this.$root.Bus.$on("change", res => {
        that.childCt = res;
      });
    }
  };
  //根组件
  new Vue({
    el: "#app",
    data: {
      Bus: new Vue()
    },
    components: {
      child
    }
  });
</script>

其中可以直接拿$root来$on 或$emit,效果一样,那为什么要创建另一个 vue 的空实例呢?,按照官网文档的说法,创建另一个 vue 空实例,用来当总线中央处理且更加清晰也便于管理

插槽

js

/*
插槽的基本使用: 默认 <slot></slot>
              具名插槽 <slot name='?'></slot>
默认的插槽由于没有名字,所以当嵌套标签时,默认会替换全部
而具名插槽就是来解决这个问题的,使用方式: slot = '对应的name' 即可替换对应的slot,从而解决上述问题
注意:但没有传入标签时,会使用默认的规定好的嵌套标签
*/
//案例
 <div id="app">
      <child>
        <span slot='header'>头</span>
        <span slot='center'>body</span>
        <span slot='footer'>脚</span>
      </child>
      <child>
        <p>1</p>    //默认  不会产生影响  因为没有名字
      </child>
      <child>
        <span>new</span>
      </child>
    </div>

    <template id="child">
      <div>
        <slot name='header'>Default</slot>
        <slot name='center'>Default</slot>
        <slot name='footer'>Default</slot>
      </div>
    </template>
    <script>
      let app = new Vue({
        el: "#app",
        components: {
          child: {
            template: "#child"
          }
        }
      });
    </script>
//2.6.0版本更新后以上语法已经废弃,新语法:
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <div id='app'>
                <child>
                    <template v-slot:header>
                    <div>title</div>
                    </template>
                    <template v-slot:body>
                    <div>body</div>
                    </template>
                    <template v-slot:footer>
                    <div>footer</div>
                    </template>
                </child>
            </div>
            <template id="container">
             <div>
                  <header>
                    <slot name="header"></slot>
                  </header>
                  <main>
                    <slot name="body"></slot>
                  </main>
                  <footer>
                    <slot name="footer"></slot>
                  </footer>
             </div>
            </template>
            <script>
                  let app = new Vue({
                    el: "#app",
                    components: {
                      child: {
                        template: "#container"
                      }
                    }
                  });
             </script>
//注意 v-slot 只能添加在一个 <template> 上

具名插槽缩写

v-slot 可以缩写成 # 后跟名称 如: v-slot:title == #title

如果写成#= “” 将无效,该缩写必须要有参数,必须按照这种格式: #default = ”{user}“

注意:以上写法在 2.6.0 以上才有效

编译作用域

js

<div id="app">
      <child v-show="isShow"></child>
    </div>
    <script>
      let app = new Vue({
        el: "div",
        components: {
          child: {
            template: `<div>
                  <span>child</span>
                  </div>`,
            data() {
              return {
                isShow: false
              };
            }
          }
        },
        data: {
          isShow: true
        }
      });
    </script>
//在组件作用域中,每个组件都有自己的作用域,上述的isShow 默认选择了vue实例的isShow,而不是子组件自己的isShow

解决作用域问题 (作用域插槽)

js

/*作用域插槽的基本使用:1.在插槽或具名插槽中绑定要传输的数据
                    2. 在父级作用域中(应用的地方),加上template标签并添加 v-slot指令,接着赋值任意的名称,最后就可以使用刚刚自定义的名称来访问传过来的数据了
                    */


    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="app">
      <child ref='child1'>
            <template #title='s'> // v-slot:title = 's'
              <span v-show='s.data' @click='toggle(s.data)'>child</span>
            </template>
      </child>
    </div>
   <script>
      let app = new Vue({
        el: "div",
        components: {
          child: {
            template: `<div>
                            <slot :data='isShow' name ='title' >
                            </slot>
                        </div>`,
            data() {
              return {
                isShow: true
              };
            },
          }
        },
        data: {
          isShow: false,
          res:null
        },
        methods:{
            toggle(s)
                {
                  console.log(this.$children,this.$refs.child1);
                  //$children $refs 都可以获取子组件数据
                  this.$refs.child1.isShow = !this.$children[0].isShow
                }
        }
      });
    </script>
//此语法只能在2.6.0版本以上使用

动态组件 & v-once & keep-live

动态组件的使用

html

//使用内置组件 component,并指定 :is 指令,:is指令指向要切换的标签
<component is:'toggle'></component>

v-once

v-once 这个指令不需要任何表达式,它的作用就是定义它的元素或组件只会渲染一次,包括元素或者组件的所有字节点。首次渲染后,不再随着数据的改变而重新渲染。也就是说使用 v-once,那么该块都将被视为静态内容。

html

//只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

<!-- 单个元素 -->
<span v-once
  >This will never change: { {msg}}< /span>
  <!-- 有子元素 -->
  <div v-once>
    <h1>comment</h1>
    <p>{ {msg}}< /p></p>
  </div>

  <!-- 组件 -->
  <my-component v-once :comment="msg"></my-component>
  <!-- `v-for` 指令-->
  <ul>
    <li v-for="i in list" v-once>{ {i}}< /li></li>
  </ul>

  //试着不要过度使用这个模式。当你需要渲染大量静态内容时,极少数的情况下它会给你带来便利,除非你非常留意渲染变慢了,不然它完全是没有必要的——再加上它在后期会带来很多困惑。例如,设想另一个开发者并不熟悉
  v-once
  或漏看了它在模板中,他们可能会花很多个小时去找出模板为什么无法正确更新。</span
>

keep-live

https://cn.vuejs.org/v2/guide/components-dynamic-async.html

https://cn.vuejs.org/v2/api/#keep-alive

  • Props
    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
    • max - 数字。最多可以缓存多少组件实例。
  • 用法<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。 当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。 在 2.2.0 及其更高版本中,activateddeactivated 将会在 <keep-alive> 树内的所有嵌套组件中触发。 主要用于保留组件状态或避免重新渲染。 html <!-- 基本 --> <keep-alive> <component :is="view"></component> </keep-alive> <!-- 多个条件判断的子组件 --> <keep-alive> <comp-a v-if="a > 1"></comp-a> <comp-b v-else></comp-b> </keep-alive> <!-- 和 `<transition>` 一起使用 --> <transition> <keep-alive> <component :is="view"></component> </keep-alive> </transition> 注意,<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。
  • include and exclude 2.1.0 新增 includeexclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示: html <!-- 逗号分隔字符串 --> <keep-alive include="a,b"> <component :is="view"></component> </keep-alive> <!-- 正则表达式 (使用 `v-bind`) --> <keep-alive :include="/a|b/"> <component :is="view"></component> </keep-alive> <!-- 数组 (使用 `v-bind`) --> <keep-alive :include="['a', 'b']"> <component :is="view"></component> </keep-alive> 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
  • max 2.5.0 新增 最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。 html <keep-alive :max="10"> <component :is="view"></component> </keep-alive> <keep-alive> 不会在函数式组件中正常工作,因为它们没有缓存实例

案例

keep-live

https://jsfiddle.net/chrisvfritz/Lp20op9o/

https://jsfiddle.net/Roam/s2erq3b6/61/

第 10 章 Vue 的生命周期

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

比如 created 钩子可以用来在一个实例被创建之后执行代码:

js

new Vue({
  data: {
    a: 1
  },
  created: function() {
    // `this` 指向 vm 实例
    console.log("a is: " + this.a);
  }
});
// => "a is: 1"

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mountedupdateddestroyed。生命周期钩子的 this 上下文指向调用它的 Vue 实例。

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

html

<div id="app">
  { { ms g }}
  <input type="text" ref="txt" v-model="msg" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      msg: "hello vue",
      dataList: []
    },
    // 在vue对象初始化过程中执行
    beforeCreate() {
      console.log("beforeCreate");
      console.log(this.msg); // undefined
    },
    // 在vue对象初始化完成后执行
    created() {
      console.log("created");
      console.log(this.msg); //hello vue
    }
    // ……
  });
</script>

第 11 章 单页应用

11.1 单页应用

  • 什么是单页应用 单页应用(single page web application,SPA),是在一个页面完成所有的业务功能,浏览器一开始会加载必需的 HTML、CSS 和 JavaScript,之后所有的操作都在这张页面完成,这一切都由 JavaScript 来控制。
  • 单页应用优缺点
    • 优点
      • 操作体验流畅
      • 完全的前端组件化
    • 缺点
      • 首次加载大量资源(可以只加载所需部分)
      • 对搜索引擎不友好
      • 开发难度相对较高

优缺点都很明显,但是我们都还没尝试过就来评价,就会显得空口无凭;接下来我们先来学习制作单页应用,然后再来进行点评;

11.2 vue 路由插件 vue-router

https://cn.vuejs.org/v2/guide/routing.html

https://router.vuejs.org/zh/

$route 当前路由信息 $router 操作路由

html

<!-- 引入路由 -->
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>

<div id="app">
  <ul>
    <li><a href="#/login">登录</a></li>
    <li><a href="#/register">注册</a></li>
  </ul>
  <!-- 路由中设置的组件会替换router-view标签 -->
  <router-view></router-view>
</div>
<script>
  // 1:定义路由组件
  var login = {
    template: "<h2>我是登录页面</h2>"
  };
  var register = {
    template: "<h2>注册有好礼</h2>"
  };

  // 2:获取路由对象
  var router = new VueRouter({
    // 定义路由规则
    routes: [
      // {请求的路径,componet是模板}
      { path: "/register", component: register },
      { path: "/login", component: login }
    ]
  });

  var app = new Vue({
    el: "#app",
    // ES6 属性简写
    // 3:将router对象传入Vue
    router
  });
</script>

上例中,在 HTML 中我们直接使用了 a 标签,但是这样并不好,因为官方为我们提供了 router-link 标签

html

<div id="app">
  <ul>
    <li><router-link to="/login">登录</router-link></li>
    <li><router-link to="/register">注册</router-link></li>

    <!-- <li><a href="#/login">登录</a></li>
        <li><a href="#/register">注册</a></li> -->

    <!-- router-link 会被解析为a标签 -->
    <!-- 
            不同的是,router-link在解析为a标签后,
            会自动为点击的 a 标签添加class属性
         -->
  </ul>
  <!-- 路由中设置的组件会替换router-view标签 -->
  <router-view></router-view>
</div>

使用 router-link 的一大好处就是,每当我们点击时,在标签内就会自动帮我们添加 class 属性,而此时,我们就可以利用 class 属性,来定义样式:

html

<style>
  .router-link-active {
    color: red;
  }
</style>

11.3 动态路由匹配

假设有一个用户列表,想要删除某一个用户,需要获取用户的 id 传入组件内,如何实现呢?

此时可以通过路由传参来实现,具体步骤如下:

  1. 通过传参,在路径上传入具体的值 html <router-link to="/users/120">用户管理</router-link>
  2. 路由规则中增加参数,在 path 最后增加 :id js { name: 'users', path: '/users/:id', component: Users },
  3. 在组件内部可以使用,this.$route 获取当前路由对象 js var Users = { template: "<div>这是用户管理内容 { { $rout e.params.id }}</div>", mounted() { console.log(this.$route.params.id); } };

第 12 章 构建一个项目

12.0 命令行工具 (CLI)

https://cn.vuejs.org/v2/guide/installation.html#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7-CLI

Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了 batteries-included 的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。更多详情可查阅 Vue CLI 的文档

12.1 初始化项目

安装 cli 命令工具:npm install -g @vue/cli @vue/cli-init

安装成功后,使用 vue -V 命令,查看版本号;

使用 vue init webpack myapp 构建一个名为 myapp 的项目:

Vue 依然使用询问的方式,让我们对项目有一个初始化的信息

  • Project name:项目名
  • Project description: 项目描述
  • Author: 作者
  • Vue build:
    • 第一种:配合大部分的开发人员
    • 第二种:仅仅中有 runtime
  • Install vue-router? 是否安装 vue-router
  • Use ESLint to lint your code?是否使用 ESLint 来验证我们的语法。
  • Pick an ESLint preser:使用哪种语法规范来检查我们的代码:
    • Standard: 标准规范
    • Airbnb: 爱彼迎规范
  • Set up unit test: 设置单元测试
  • Setup e2e tests: 设置端对端测试
  • Should we run ‘npm install’:要不要帮忙你下载这个项目需要的第三方包
    • 使用 npm 来下载
    • 使用 yarn 来下载

shell

To get started:

  cd myapps
  npm run dev   // 使用命令启动项目

  -----
  Your application is running here: http://localhost:8080

  打开浏览器,访问 http://localhost:8080
  看到浏览器的欢迎界面,表示项目运行成功

12.2 项目结构介绍

├── build                webpack打包相关配置文件目录
├── config                webpack打包相关配置文件目录
├── node_modules         第三方包
├── src                    项目源码(主战场)
│   ├── assets             存储静态资源,例如 css、img、fonts
│   ├── components         存储所有公共组件
│   ├── router             路由
│   ├── App.vue             单页面应用程序的根组件
│   └── main.js             程序入口,负责把根组件替换到根节点
├── static                可以放一些静态资源
│   └── .gitkeep         git提交的时候空文件夹不会提交,这个文件可以让空文件夹可以提交
├── .babelrc             配置文件,es6转es5配置文件,给 babel 编译器用的
├── .editorconfig         给编辑器看的
├── .eslintignore          给eslint代码风格校验工具使用的,用来配置忽略代码风格校验的文件或是目录
├── .eslintrc.js         给eslint代码风格校验工具使用的,用来配置代码风格校验规则
├── .gitignore             给git使用的,用来配置忽略上传的文件
├── index.html             单页面应用程序的单页
├── package.json         项目说明,用来保存依赖项等信息
├── package-lock.json      锁定第三方包的版本,以及保存包的下载地址
├── .postcssrc.js          给postcss用的,postcss类似于 less、sass 预处理器
└── README.md             项目说明文档

12.3 语法检查

注意 :如果我们在 构建项目时 选择了 Use ESLint to lint your code 那么我们在写代码时必须严格遵守 JavaScript Standard Style 代码风格的语法规则:

  • 使用两个空格 – 进行缩进
  • 字符串使用单引号 – 需要转义的地方除外
  • 不再有冗余的变量 – 这是导致 大量 bug 的源头!
  • 无分号没什么不好。不骗你!
  • 行首不要以 (, [, or ``` 开头
    • 这是省略分号时唯一会造成问题的地方 – 工具里已加了自动检测!
    • 详情
  • 关键字后加空格 if (condition) { ... }
  • 函数名后加空格 function name (arg) { ... }
  • 坚持使用全等 === 摒弃 == 一但在需要检查 null || undefined 时可以使用 obj == null
  • 一定要处理 Node.js 中错误回调传递进来的 err 参数。
  • 使用浏览器全局变量时加上 window 前缀 – documentnavigator 除外
    • 避免无意中使用到了这些命名看上去很普通的全局变量, open, length, event 还有 name

说了那么多,看看这个遵循了 Standard 规范的示例文件 中的代码吧。或者,这里还有一大波使用了此规范的项目 代码可供参考。

注意: 如果你不适应这些语法规则,可以在构建项目时不使用 ESLint 的语法检查

12.4 项目代码预览

12.4.1 知识储备

严格模式

http://javascript.ruanyifeng.com/advanced/strict.html

严格模式主要有以下限制。

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface
ES6 模块化

http://es6.ruanyifeng.com/#docs/module

总结:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口;
  • ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
  • ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块;

12.4.2 代码加载执行

main.js

js

// 入口文件

// 以es6模块的方式引入 vue APP router 三个模块;
import Vue from "vue";
import App from "./App";
import router from "./router";

Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  // 获取节点对象
  el: "#app",
  // 引入路由
  router,
  // 本实例的私有组件
  components: { App },
  // el 与 template 在同一个实例中出现,
  // 根据生命周期的执行顺序可知,template中的内容会替换el选中的内容
  template: "<App/>"
});

roter/index.js

js

import Vue from "vue";
import Router from "vue-router";
import HelloWorld from "@/components/HelloWorld";

// Vue 中插件引入语法
// https://cn.vuejs.org/v2/guide/plugins.html
Vue.use(Router);

// ES6模块导出语法
export default new Router({
  routes: [
    // 定义一个路由规则
    {
      path: "/", // 请求路径
      name: "HelloWorld", // 路由名称标识
      component: HelloWorld //请求此路由时,使用的组件
    }
  ]
});

components/HelloWorld.vue

js

export default {
  // 模块名字
  name: "HelloWorld",
  // 组件中 data 数据必须是一个有返回值的方法
  data() {
    return {
      msg: "Welcome to Your Vue.js App"
    };
  }
};
(main.js->template: '<App/>')替换 (index.html->div#app);

(index.html-><App/>) --> (components: { App })

( components: { App }) --> (import App from './App' -> src/App.vue)

(App.vue -> <router-view/> -> 路由组件) --> (main.js-> router)
========此项决定了页面展示那个组件内容 ========

({path: '/',name: 'HelloWorld', component: HelloWorld }) --> (import HelloWorld from '@/components/HelloWorld')

(src/components/HelloWorld.vue) --> <router-view/>

12.5 添加自己的路由组件

修改 router/index.js ,添加自己的路由

js

import Vue from "vue";
import Router from "vue-router";
import HelloWorld from "@/components/HelloWorld";
// 引入(导入) 组件
import MyRouter from "@/components/MyRouter";

Vue.use(Router);

// ES6模块导出语法
export default new Router({
  routes: [
    { path: "/", name: "HelloWorld", component: HelloWorld },
    // 添加自己的路由及组件
    {
      path: "/myrouter",
      name: "MyRouter",
      component: MyRouter
    }
  ]
});

在 components 文件夹中添加 MyRouter.vue 文件,写自己的组件代码:

html

<template>
  <div class="mypage">
    { {mydatas} }
  </div>
</template>

<script>
  // 模块化导出
  export default {
    data() {
      return { mydatas: "lksadjflks" };
    }
  };
</script>

<style>
  .mypage {
    width: 200px;
    height: 50px;
    background: pink;
  }
</style>

浏览器渲染效果如下:

第 13 章 Vuex

流程:组件->Actions->Mutations->State->组件

严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true

js

const store = new Vuex.Store({
  // ...
  strict: true
});

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

#开发环境与发布环境

不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。

类似于插件,我们可以让构建工具来处理这种情况:

js

const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== "production"
});

基本使用/介绍

state——存储、数据 mutation——修改数据、追踪;同步 action——封装:组合;异步

js

//安装
npm install vuex
//加载
import Vuex from "vuex"

//也可以在vue-cli中单独添加一个文件类似vue-router一样,命名为store(脚手架环境)
import Vue from "vue"
import Vuex form 'vuex'
Vue.use(Vuex)

//基本使用/介绍

//跟router一样,要new一个vuex
let store = new Vuex.Store({
//1.strict是否开启严格模式,
    strice:true,
//2.state 存放数据的属性 vuex核心
    state:{
      ...data
    },
//3.改变state时必须经过的属性
    muations:{
        ...function(state,...参数)
    },
/*4.Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。*/
    actions:{
        ...function(state || {commit},参数)
    },
//5.类似vue的计算computed,主要处理一些需要计算、总和的操作
    getters:{
        ...function(state){
            return state.?+state.?
        }
    },
//6.类似路由组件,可以分成很多个模块引入加载
    modules:{
        ...vuex模块
    }
})

辅助方法

mapState state -> computed mapActions actions -> methods mapGetters getters -> computed mapMutations mutations -> methods

映射

这 3 个方法可以实现自动把 vuex 的 state,actions,getters,加载到 vue 组件里使用,简化了中间手动繁琐的操作

js

//使用之前必须从vuex里引入这些方法:import {mapState, mapActions, mapGetters} from 'vuex';

computed:{
    ...mapState([vuex里面的state]),
    ...mapGetters([vuex里面的getters])
}
methods:{
    ...mapActions([vuex里面的actions])
    ...mapMutations([vuex里面的mutations])
}
//也可以传入一个对象,用于命别名
    ...mapActions({
      a:'fun'
    })

基本使用(综合案例)

vuex 配置

js

import Vue from "vue";
import Vuex from "vuex";

import ModA from "./mod_a";
import ModB from "./mod_b";

Vue.use(Vuex);

//vuex3-声明store对象
export default new Vuex.Store({
  strict: process.env.NODE_ENV != "production", //严格模式:防止直接修改state
  state: {
    //核心:数据
    a: 12,
    b: 5,
    users: []
  },
  mutations: {
    addA(state, n) {
      state.a += n;
    },
    addB(state, n) {
      state.b += n;
    },
    setOnline(state, id) {
      state.users.forEach(user => {
        if (user.id == id) {
          user.online = true;
        }
      });
    },
    setUsers(state, users) {
      state.users = users;
    }
  },
  actions: {
    addA({ commit }, n) {
      commit("addA", n);
    },
    addB({ commit }, n) {
      commit("addB", n);
    },
    setOnline({ commit }, id) {
      commit("setOnline", id);
    },
    async readUsers({ commit }) {
      let res = await fetch("http://localhost:8081/user.txt");
      let users = await res.json();

      commit("setUsers", users);
    }
  },
  getters: {
    count(state) {
      return state.a + state.b;
    },
    onlineUsers(state) {
      return state.users.filter(user => user.online);
    }
  },
  modules: {
    mod_a: ModA,
    mod_b: ModB
  }
});

调用 vuex

html

<template>
  <div>
    <div>a: { {a}}< /div>
    <div>b: { {b}}< /div>
    <div>count: { {count}}< /div>
    <input type="button" value="a+5" @click="addA(5)" />
    <input type="button" value="b+3" @click="addB(3)" />

    <br>
    str: { {$stor e.state.str}}<br>
    a_str: { {str_a}} <br>
    b_str: { {str_b}} <br>
    <input type="button" value="设置A" @click="set_a('aaa')">
    <input type="button" value="设置B" @click="set_b('bbb')">
    <br>

    <input type="button" value="张三出现" @click="setOnline(5)" />
    <ul>
      <li v-for="user in onlineUsers">
        名字:{ {user.nam e}}
        年龄:{ {user.ag e}}
      </li>
    </ul>
  </div>
</template>

<script>
import Table from '@/components/common/Table';
import Cmp1 from '@/components/Cmp1';
import {mapState, mapActions, mapGetters} from 'vuex';

export default {
  name: 'Index',
  data () {
    return {
      fields: [
        {name: 'ID', text: 'ID'},
        {name: 'name', text: '姓名'},
        {name: 'age', text: '年龄'},
      ],
      datas: [
        {ID: 1, name: 'blue', age: 18},
        {ID: 2, name: '张三', age: 25},
        {ID: 4, name: 'tom', age: 8},
      ]
    }
  },
  async created(){
    await this.readUsers();

    //this.setStr('sdfasdfsdg');
  },
  methods: {
    del(id){
      this.datas=this.datas.filter(data=>data.ID!=id);
    },
    ...mapActions(['addA', 'addB', 'setOnline', 'readUsers']),
    //...mapActions(['setStr'])
    ...mapActions({
      set_a: 'mod_a.setStr',
      set_b: 'mod_b.setStr'
    })
    // set_a(){
    //   this.$store.dispatch('mod_a.setStr', 'aaa');
    // },
    // set_b(){
    //   this.$store.dispatch('mod_b.setStr', 'bbb');
    // }
  },
  components: {
    Table, Cmp1
  },
  computed: {
    ...mapState(['a', 'b']),
    ...mapState({
      str_a: state=>state.mod_a.str,
      str_b: state=>state.mod_b.str,
    }),
    ...mapGetters(['count', 'onlineUsers'])
  }
}
</script>

<style scoped>
</style>

Vue 前后端分离项目

第 0 章 项目如何开始的

0.1 总体流程

需求调研–>需求转为需求文档–>将需求文档转为开发文档–>前端文档–>后台文档–>项目测试–>打包上线

0.2 数据服务器构建

0.2.1 技术栈

Vue+elementUI+NodeJS+MySQL

0.2.2 数据服务器准备

导入数据库数据:打开数据库服务器,新建名为 itcast 的库;

后台为我们提供了 /api-server/db/mydb.sql 数据文件,打开复制 sql 语句直接运行即可;

然后在 api-server 中执行 npm install 安装服务器所需扩展模块;

node app.js 将服务器启动起来;

0.3 接口测试

0.3.1 登录

后台已经写好接口文档,根据文档中的表述,我们测试登录接口:

0.3.2 获取用户信息

请求用户列表数据;但是,并没有返回相应的数据;

使用 token 替换 cookie 的功能

0.4 Vue 项目初始化

使用 vue-cli 工具初始化项目:

初始化成功,使用 npm run dev 启动项目;

0.5 项目预览

解压 my-project(Vue项目).rar 后进入目录,使用 npm run dev 启动项目;

第 1 章 开始项目

1.1 添加用户登录路由组件

添加路由:myapp-code/src/router/index.js

js

import Vue from "vue";
import Router from "vue-router";
import Login from "@/components/login/login";

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: "/login",
      name: "Login",
      component: Login
    }
  ]
});

添加组件:myapp-code/src/components/login/login.vue

html

<template>
  <div>{ {msg}}< /div>
</template>

<script>
  export default{
    data(){
      return {msg:'我是登录页面'}
    }
  }
</script>
<style>
</style>

修改 Vue 项目运行端口: myapp-code/config/index.js

1.2 使用 ElementUI

http://element-cn.eleme.io/#/zh-CN

修改 src/main.js 代码,全局引入 ElementUI ;

js

import Vue from "vue";
import App from "./App";
import router from "./router";

// 引入 ElementUI
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
// 将 ElementUI 注册为 vue的全局组件
Vue.use(ElementUI);

Vue.config.productionTip = false;

new Vue({
  el: "#app",
  router,
  components: { App },
  template: "<App/>"
});

在我们登录页面中尝试一下:src/components/login/login.vue

html

<template>
  <div>
    <el-button type="success">成功按钮</el-button>
    <el-button type="info">信息按钮</el-button>
    <el-button type="warning">警告按钮</el-button>
    <el-button type="danger">危险按钮</el-button>
  </div>
</template>

1.3 搭建登录页面

把公共样式写到 src/assets/css/style.cssForm 表单

css

html,
body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
}

然后在 src/main.js 加载公共样式:

javascript

// 代码略...

// 引入我们的公共样式
import "./assets/css/style.css";

// 代码略...

为了让登陆组件的背景色撑满,所以我们需要让他们的父盒子 div#app 高度设置为 100%

所以我们在 src/App.vue

css

<style>
#app {
  height: 100%;
}
</style>

接下来我们开始调整 src/components/login/login.vue 组件样式:

  • 注意:这里遵循一个原则,不要直接去使用 Element 组件自带的类名
  • 如果你想为 Element 组件添加自定义样式,那么建议你给它加你自己的类名来控制

html

<template>
  <div class="login-wrap">
    <el-form
      ref="form"
      :label-position="labelPosition"
      :model="form"
      label-width="80px"
      class="login-from"
    >
      <h2>用户登录</h2>
      <el-form-item label="用户名">
        <el-input v-model="form.name"></el-input>
      </el-form-item>
      <el-form-item label-position="top" label="密码">
        <el-input v-model="form.pwd"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit" class="login-btn"
          >登录</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        labelPosition: "top",
        form: {
          name: "",
          pwd: ""
        }
      };
    },
    methods: {
      onSubmit() {}
    }
  };
</script>

css

<style>
.login-wrap {
  background-color: #324152;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.login-wrap .login-from {
  background-color: #fff;
  width: 400px;
  padding: 30px;
  border-radius: 5px;
}

.login-wrap .login-from .login-btn {
  width: 100%;
}
</style>

1.4 完成登录功能

1.4.1 封装 axios

vue 插件语法: https://cn.vuejs.org/v2/guide/plugins.html

Axios : https://www.kancloud.cn/yunye/axios/234845

npm install axios ,将 axios 进行模块化封装,以 Vue 插件的方式,全局引入:

将插件的封装写入 src/assets/js/myaxios.js

js

// 引入axios
import Axios from "axios";
// 自定义插件对象
var myaxios = {};
myaxios.install = function(vue) {
  // 设置axios请求的URL,此后axios发送的请求全部执行本地址
  var axios_obj = Axios.create({
    baseURL: "http://localhost:8888/api/private/v1/"
  });
  // 将设置好的axios对象赋值给Vue实例的原型
  // 之后可以在Vue中直接只用 this.$myHttp 使用axios发送请求
  vue.prototype.$myHttp = axios_obj;
};
// 将插件以 模块 方式导出
export default myaxios;

在 main.js 引入 axios 插件,并注册为全局插件

js

// 导入 myaxios 模块
import myaxios from "@/assets/js/myaxios.js";
Vue.use(myaxios); // 注册使用 axios 插件

1.4.2 完成登录功能

发送 post 请求

js

export default {
  data() {
    return {
      labelPosition: "top",
      form: {
        username: "",
        password: ""
      }
    };
  },
  methods: {
    // 修改组件中绑定的按钮名称为 onLogin
    onLogin() {
      // 使用axios 发送post 请求,传入data中的form数据
      this.$myHttp.post("login", this.form).then(backdata => {
        // 异步执行成功后
        console.log(backdata);
      });
    }
  }
};

继续修改代码,完成登录逻辑

vue-router 编程式导航: https://router.vuejs.org/zh/guide/essentials/navigation.html

js

onLogin(){
    // 使用axios 发送post 请求,传入data中的form数据
    this.$myHttp.post('login',this.form)
        .then(backdata=>{ // 异步执行成功后
        //console.log(backdata.data);
        // 结构赋值,获取返回的数据
        var {data,meta}  = backdata.data;
        // 判断数据状态
        if(meta.status == 200){
            alert('登录成功');
            // 使用vue-router编程式导航跳转到home
            this.$router.push('home');
        }
    });
}
//注意:push会产生历史记录,因此可以返回,当一些操作不想让撤回时可以使用replace,同时也不会产生历史记录

修改提示弹窗

js

var { data, meta } = backdata.data;
// 判断数据状态
if (meta.status == 200) {
  this.$message({
    message: "恭喜你,登录成功",
    type: "success"
  });
  // 使用vue-router编程式导航跳转到home
  this.$router.push("home");
} else {
  this.$message.error("错了哦");
}

1.4.3 表单验证

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。

js

data() {
    return {
      labelPosition: "top",
      form: {
        username: "",
        password: ""
      },
      // 与 el-form 中的 :rules="rules"  对应
      rules: {
        //与 el-form-item 中的 prop="username" 对应
        username: [
          // 验证规则  是否必须        提示信息            触发时机
          { required: true, message: "请输入用户名", trigger: "blur" }
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
        ]
      }
    };
  },

1.4.4 阻止数据提交

js

onLogin() {
    // 因为要获取form表单的节点对象,
    // 所以 el-form 中要加入 ref="ruleForm"
    this.$refs.ruleForm.validate(valid => {
        // elementUI 会将 validate 方法加入到节点对象,
        // 在提交是,如果表单的验证未通过,会将错误信息传入回调函数
        if (!valid) {
            // 如果有表单错误信息,则无反应
            this.$message.error("输入有误");
            return;
        }
        // 使用axios 发送post 请求,传入data中的form数据
        this.$myHttp.post("login", this.form).then(backdata => {
            // 结构赋值,获取返回的数据
            var { data, meta } = backdata.data;
            // 判断数据状态
            if (meta.status == 200) {
                this.$message({
                    message: "恭喜你,登录成功",
                    type: "success"
                });
                // 使用vue-router编程式导航跳转到home
                this.$router.push("home");
            } else {
                this.$message.error("错了哦");
            }
        });
    });
}

1.5 首页

1.5.1 添加路由及页面布局

修改登录成功后逻辑,使用路由名称表示进行跳转:

js

// 使用vue-router编程式导航跳转到home
this.$router.push({ name: "home" });

导入组件,添加路由 src/router/index.js

js

import Home from '@/components/home/home'
 ……
{
    path:'/',
    name:'home',
    component:Home
}

添加一个 home 组件src/components/home/home.vue

html

<template>
  <div>{ {msg}}< /div>
</template>

<script>
export default {
  data(){
    return{
      msg:'we'
    }
  }
}
</script>

修改一个 home 组件src/components/home/home.vue Container 布局容器

html

<template>
  <el-container class="height100">
    <el-header>Header</el-header>
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-main>Main</el-main>
    </el-container>
  </el-container>
</template>

css

.height100 {
  height: 100%;
}
.el-header {
  background-color: #b3c0d1;
  color: #333;
  text-align: center;
  line-height: 60px;
}
.el-aside {
  background-color: #d3dce6;
  color: #333;
  text-align: center;
  line-height: 200px;
}
.el-main {
  background-color: #e9eef3;
  color: #333;
  text-align: center;
  line-height: 160px;
}

1.5.2 头部样式

/src/components/home/home.vue

Layout 布局

html

<el-header>
  <el-row>
    <el-col :span="6">
      <div class="grid-content bg-purple">
        <img src="/static/logo.png" alt="" />
      </div>
    </el-col>
    <el-col :span="12"
      ><div class="grid-content bg-purple-light">电商后台管理系统</div></el-col
    >
    <el-col :span="6"
      ><div class="grid-content bg-purple">
        <el-button type="warning">退出</el-button>
      </div></el-col
    >
  </el-row>
</el-header>

…… // 标题文本样式 .bg-purple-light { font-size: 25px; color: white; }

1.5.3 左侧样式

/src/components/home/home.vue

NavMenu 导航菜单 Icon 图标

html

<el-container>
  <el-aside width="200px">
    <!-- 
        el-menu 侧边导航栏组件
            unique-opened="true" 只保持一个导航开启
            router="true" 开启导航路由
        el-submenu 导航栏的顶级项
        template 导航栏中需要展示的内容
            i 图标
            span 文字
        el-menu-item-group 次级导航组 内容与导航的组标识 可直接删除
        el-menu-item  导航栏选项

          index属性 控制收起展开+路由标识:
              在el-menu中加入router=“true”属性;
              index="1-1" 点击时路由跳转到1-1 ;
      -->
    <el-menu
      :unique-opened="true"
      :router="true"
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
    >
      <el-submenu index="1">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>用户管理</span>
        </template>
        <el-menu-item index="1-1">
          <i class="el-icon-menu"></i>
          用户列表
        </el-menu-item>
      </el-submenu>

      <el-submenu index="2">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>权限管理</span>
        </template>
        <el-menu-item index="2-1">
          <i class="el-icon-menu"></i>
          角色列表
        </el-menu-item>
        <el-menu-item index="2-2">
          <i class="el-icon-menu"></i>
          权限列表
        </el-menu-item>
      </el-submenu>
    </el-menu>
  </el-aside>
  <el-main>Main</el-main>
</el-container>

我们发现,大部分组件,在浏览器渲染后,都会在标签内部自动添加一个标签名为名字的 class 属性 ,我们可以利用这个属性,设置样式:

css

.el-menu {
  width: 200px;
  height: 100%;
}

.el-submenu {
  text-align: left;
}

1.5.4 右侧内容

添加组件内容 /src/components/home/home.vue

html

<el-main>
  <!-- 路由组件 -->
  <router-view></router-view>
</el-main>

添加组件:src/components/index.vue

html

<template>
  <p>我是首页内容</p>
</template>

<script>
  export default {};
</script>

<style></style>

添加路由:/src/router/index.js

js

{
    path:'/index',
    name:'index',
    component:Index
}

注意: 我们希望 index.vue 组件的内容,展示到 home 组件的中

知识补充:

此时,我们需要借助嵌套路由: https://router.vuejs.org/zh/guide/essentials/nested-routes.html

嵌套路由(子路由的基本用法):

html

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<div id="app">
  <router-view></router-view>
</div>
<script>
  // 1:定义路由组件
  var login = {
    template: `
            <div>
                <h2>我是登录页面</h2>
                <li><router-link to="/zi">子路由</router-link></li>
                <!--路由组件中继续使用路由组件-->
                <router-view></router-view>
            </div>
            `
  };
  var zi = {
    template: "<h4>我是嵌套路由的组件</h4>"
  };
  // 2:获取路由对象
  var router = new VueRouter({
    // 定义路由规则
    routes: [
      {
        name: "login",
        path: "/login",
        component: login,
        // 路由中的 children 属性,定义嵌套路由(子路由)
        children: [{ name: "zi", path: "/zi", component: zi }]
      }
    ]
  });

  var app = new Vue({
    el: "#app",
    router
  });
</script>

*再谈 代码加载流程 *

(main.js->template: '<App/>')替换 (index.html->div#app);

(index.html-><App/>) --> (components: { App })

( components: { App }) --> (import App from './App' -> src/App.vue)

(App.vue -> <router-view/> -> 路由组件) --> (main.js-> router)
========此项决定了页面展示那个组件内容 ========

({path: '/',name: 'HelloWorld', component: HelloWorld }) --> (import HelloWorld from '@/components/HelloWorld')

(src/components/HelloWorld.vue) --> <router-view/>

因此,我们需要让 index 成为 home 的子路由组件 src/router/index.js

js

routes: [
  {
    path: "/login",
    name: "Login",
    component: Login
  },
  {
    path: "/",
    name: "home",
    component: Home,
    // 添加子路由
    children: [{ path: "index", name: "index", component: Index }]
  }
];

登录完成后,跳转到 home–>index src/components/login/login.vue

js

if (meta.status == 200) {
  this.$message({
    message: "恭喜你,登录成功",
    type: "success"
  });
  // 使用vue-router编程式导航跳转到home->index
  this.$router.push({ name: "index" });
}

1.6 验证首页登录

src/components/login/login.vue

js

if (meta.status == 200) {
  this.$message({
    message: "恭喜你,登录成功",
    type: "success"
  });

  // 登录成功后,将token信息保存到 localStorage
  window.localStorage.setItem("token", data.token);

  // 使用vue-router编程式导航跳转到home->index
  this.$router.push({ name: "index" });
}

src/components/home/home.vue 验证登录

js

export default {
  // 使用生命周期的钩子函数,判断token
  mounted() {
    // 获取token
    var token = window.localStorage.getItem("token");
    if (!token) {
      // 错误提示
      this.$message.error("请登录");
      // 跳转到登录页面
      this.$router.push({ name: "Login" });
    }
  },

  data() {
    return {
      msg: "we"
    };
  }
};

1.7 用户退出

绑定点击事件

html

<el-col :span="6"
  ><div class="grid-content bg-purple">
    <el-button @click="loginOut" type="warning">退出</el-button>
  </div></el-col
>

js

methods:{
    loginOut(){
        // 清楚token
        window.localStorage.removeItem('token')
        // 退出提示
        this.$message({
            message: "您已经退出,继续操作请重新登录",
            type: "success"
        });
        // 页面路由跳转
        this.$router.push({ name: "Login" });
    }
}

第 2 章 用户管理

2.1 路由及组件

/src/components/home/home.vue

html

<el-menu-item index="users">
  <i class="el-icon-menu"></i>
  用户列表
</el-menu-item>

src/router/index.js

js

import Users from '@/components/users/users'

……

children:[
    {path:'index',name:'index',component:Index},
    {path:'users',name:'users',component:Users}
]

src/components/users/users.vue

html

<template>
  <div>展示用户列表表格</div>
</template>

<script>
  export default {};
</script>

<style></style>

2.2 面包屑导航及搜索框

src/components/users/users.vue Card 卡片 Breadcrumb 面包屑 Input 输入框 Button 按钮

html

<template>
  <div>
    <!-- 面包鞋 -->
    <el-card>
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <el-breadcrumb-item :to="{ path: '/index' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item>用户管理</el-breadcrumb-item>
        <el-breadcrumb-item>用户列表</el-breadcrumb-item>
      </el-breadcrumb>
    </el-card>
  </div>
</template>

html

……
</el-card>

<el-row>
  <el-col :span="6" class="sou">
    <el-input placeholder="请输入内容" v-model="input5" class="input-with-select">
      <el-button slot="append" icon="el-icon-search"></el-button>
    </el-input>
  </el-col>
  <el-col :span="1" class="sou">
      <el-button type="success" plain>添加用户</el-button>
    </el-col>
</el-row>

</div>

……

<script>
export default {
  data(){
    // 不想看到报错
    return{input5:''}
  }
};
</script>

<style>
.sou{
  line-height:30px
}
</style>

2.3 展示用户列表

2.3.4 组件展示

src/components/users/users.vue Table 表格->自定义索引

html

<!-- 表格 自定义索引 -->
<el-table
    :data="tableData"
    style="width: 100% ;">
    <el-table-column
      type="index"
      :index="indexMethod">
    </el-table-column>
    <el-table-column
      prop="date"
      label="日期"
      width="180">
    </el-table-column>
    <el-table-column
      prop="name"
      label="姓名"
      width="180">
    </el-table-column>
  </el-table>

</div>
</template>

<script>
export default {
  data() {
    return {
      input5:'',
      tableData: [
        {
          date: "2016-05-03",
          name: "王小虎"
        }
      ]
    };
  }
};
</script>
<style>
.sou {
  line-height: 30px;
}

.el-main{
  line-height:30px;
}
</style>

2.3.5 获取数据

出登录接口,其他接口发送 http 请求,必须携带 token 值

Axios : https://www.kancloud.cn/yunye/axios/234845 —> 请求配置

js

data() {
    return {
        input5:'',// 不想看到报错
        // 设置页码及条数
        pagenum:1,
        pagesize:5,
        tableData: []
    };
},
// 利用钩子函数,获取数据
mounted() {
    // 获取token
    let token = window.localStorage.getItem('token');
    // 通过配置选项发送请求
    // 携带token
    this.$myHttp({
        // 设置链接地址 es6新语法
        url:`users?pagenum=${this.pagenum}&pagesize=${this.pagesize}`,
        method:'get',
        // 配置token
        headers: {'Authorization': token}
    }).then(res=>{
        // 修改数据  展示页面
        this.tableData = res.data.data.users;
    })
},

修改组件参数,展示数据:

<el-table-column prop="username" label="姓名" > </el-table-column>

2.3.6 操作按钮

Button 按钮 Table 表格->自定义列模板

html

<el-table-column label="操作" width="210">
  <template slot-scope="scope">
    <el-button type="primary" icon="el-icon-edit" size="mini" plain></el-button>
    <el-button
      type="primary"
      icon="el-icon-check"
      size="mini"
      plain
    ></el-button>
    <el-button
      type="primary"
      icon="el-icon-delete"
      size="mini"
      plain
    ></el-button>
  </template>
</el-table-column>

表格中加入按钮等元素时,需要使用 template 进行包裹:

html

<el-table-column label="用户状态" width="210">
  <template slot-scope="scope">
    <el-switch v-model="value2" active-color="#13ce66" inactive-color="#ff4949">
    </el-switch>
  </template>
</el-table-column>

2.3.7 状态显示

而在template 标签中有一个 slot-scope="scope" 属性,scope 的值就是本列中所有数据的值,参考: Table 表格->固定列

html

<el-table-column label="用户状态" width="210">
  <template slot-scope="scope">
    <!-- 利用scope 中的值,争取显示用户状态 -->
    <el-switch
      v-model="scope.row.mg_state"
      active-color="#13ce66"
      inactive-color="#ff4949"
    ></el-switch>
    <!-- 测试事件,查看 scope 数据 -->
    <el-button type="primary" size="mini" @click="showScope(scope)"
      >显示scope</el-button
    >
  </template>
</el-table-column>

js

methods:{
    // 测试 方法 显示scope
    showScope(scope){
      console.log(scope);
    }
  },

2.3.8 分页展示

Pagination 分页->附加功能

html

  <!-- 分页 -->
  <!--
      current-page  当前页码数
      page-sizes  显示条数选项
      page-size 当前每页条数
  -->
  <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="pagenum"
      :page-sizes="[2, 20, 40]"
      :page-size="pagesize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total">
    </el-pagination>
</div>
</template>

……
<script>
   ……
data() {
    return {
      input5:'',// 不想看到报错

      pagenum:1, //设置页码
      pagesize:2, // 设置页条数
      total:0,  //显示总条数

      tableData: []
    };
  },
      ……
      ……
         // 获取总条数 修改数据展示
        this.total = res.data.data.total;

但点击页码时,会触发 size-change 事件

js

<script>
export default {
  data() {
    return {
      input5: "", // 不想看到报错
      pagenum: 1, //设置页码
      pagesize: 2, // 设置页条数
      total: 0, //显示总条数
      tableData: []
    };
  },

  methods: {
    // 获取用户数据
    getUserData() {
      // 获取token
      let token = window.localStorage.getItem("token");
      // 通过配置选项发送请求
      // 携带token
      this.$myHttp({
        // 设置链接地址 es6新语法
        url: `users?pagenum=${this.pagenum}&pagesize=${this.pagesize}`,
        method: "get",
        // 配置token
        headers: { Authorization: token }
      }).then(res => {
        // 修改数据  展示页面
        this.tableData = res.data.data.users;
        // 获取总条数 修改数据展示
        this.total = res.data.data.total;
      });
    },

    // 点击页码触发
    handleCurrentChange(pages) {
        // console.log(pages);
        // 修改data数据,重新发送请求
        this.pagenum = pages;
        this.getUserData();
    },
    // 改变显示条数时触发
    handleSizeChange(numbers){
      this.pagesize = numbers;
      this.getUserData();
    }
  },

  // 利用钩子函数,获取数据
  mounted() {
    this.getUserData();
  }
};
</script>

2.4 模糊搜索

请求地址中加入 query 请求参数,获取条件结果

html

<el-input placeholder="请输入内容" v-model="search" class="input-with-select">
  <el-button
    slot="append"
    @click="searchUsers"
    icon="el-icon-search"
  ></el-button>
</el-input>

……

<script>
  data() {
      return {
        search: "", // 搜索关键字
      };
    },

        // 请求地址中加入关键字
        url: `users?pagenum=${this.pagenum}&pagesize=${this.pagesize}&query=${this.search}`,


        // 点击搜索事件
        searchUsers(){
            this.getUserData();
        }
</script>

2.5 切换用户状态

html

<!-- 利用scope 中的值,争取显示用户状态 -->
<!-- 组件自带change事件 -->
<el-switch
  v-model="scope.row.mg_state"
  @change="change(scope)"
  active-color="#13ce66"
  inactive-color="#ff4949"
></el-switch>

js

// Switch 开关 组件自带事件
change(scope){
    // 接受本条全部信息
    // console.log(scope)
    let id = scope.row.id; // 获取id
    var state = scope.row.mg_state; // 获取修改后的状态
    // 请求接口
    this.$myHttp.put(`users/${id}/state/${state}`)
        .then(res=>{
        // 修改失败,将状态改为原始值
        if(!res.data.data){
            this.tableData[scope.$index].mg_state = !state;
            this.$message.error("修改失败");
        }
    })
}

修改失败是因为没有 token:

js

// Switch 开关 组件自带事件
change(scope){
    // 接受本条全部信息
    // console.log(scope)
    let id = scope.row.id; // 获取id
    var state = scope.row.mg_state; // 获取修改后的状态
    // 请求接口
    // 需要使用配置参数请求,设置token
    this.$myHttp({
        url:`users/${id}/state/${state}`,
        method:'put',
        headers: { Authorization: window.localStorage.getItem("token") }
    })
        .then(res=>{
        // 修改失败,将状态改为原始值
        if(!res.data.data){
            this.tableData[scope.$index].mg_state = !state;
            this.$message.error("修改失败");
        }
    })
}

2.6 删除用户

MessageBox 弹框->确认消息

js

// 组件中绑定点击按钮
<el-button type="primary" icon="el-icon-delete" size="mini" @click="deleteUser(scope.row.id)" plain></el-button>


// 删除用户
deleteUser(id) {
    //   this.$myHttp({
    //     url: `users/${id}`,
    //     method: "delete",
    //     headers: { Authorization: window.localStorage.getItem("token") }
    //   }).then(res => {
    //     this.getUserData();
    //     this.$message({
    //       message: "删除成功",
    //       type: "success"
    //     });
    //   });
    this.$confirm("此操作将永久删除该用户, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
    })
        .then(() => {
        this.$myHttp({
            url: `users/${id}`,
            method: "delete",
            headers: { Authorization: window.localStorage.getItem("token") }
        }).then(res => {
            this.getUserData();
            this.$message({
                message: "删除成功",
                type: "success"
            });
        });
    })
        .catch(() => {
        this.$message({
            type: "info",
            message: "已取消删除"
        });
    });
}

2.7 添加用户

Dialog对话框->自定义内容->打开嵌套表单的 Dialog Form 表单

表单弹窗:

html

<el-col :span="1" class="sou">
  <!-- 绑定按钮点击事件 直接将 dialogFormVisible值设置为true显示窗口  -->
  <el-button type="success" @click="dialogFormVisible = true" >添加用户</el-button>
  <!--
    :visible.sync属性 控制窗口显示隐藏
    -->
  <el-dialog title="收货地址" :visible.sync="dialogFormVisible">
    <el-form :model="form">
      <el-form-item label="活动名称" :label-width="formLabelWidth">
        <el-input v-model="form.name" autocomplete="off"></el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
      <el-button @click="dialogFormVisible = false">取 消</el-button>
      <el-button type="primary" @click="dialogFormVisible = false">确 定</el-button>
    </div>
  </el-dialog>

  </el-col>
</el-row>

修改表单

html

<el-col :span="1" class="sou">
  <!-- 绑定按钮点击事件 直接将 dialogFormVisible值设置为true显示窗口  -->
  <el-button type="success" @click="dialogFormVisible = true"
    >添加用户</el-button
  >
  <!-- 
    :visible.sync属性 控制窗口显示隐藏
    -->
  <el-dialog title="添加用户" :visible.sync="dialogFormVisible">
    <el-form :model="form">
      <el-form-item label="姓名" label-width="90px">
        <el-input v-model="form.username"></el-input>
      </el-form-item>
      <el-form-item label="密码" label-width="90px">
        <el-input v-model="form.password"></el-input>
      </el-form-item>
      <el-form-item label="邮箱" label-width="90px">
        <el-input v-model="form.email"></el-input>
      </el-form-item>
      <el-form-item label="电话" label-width="90px">
        <el-input v-model="form.mobile"></el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
      <el-button @click="dialogFormVisible = false">取 消</el-button>
      <!-- 修改点击事件,在数据入库成功后关闭窗口 -->
      <el-button type="primary" @click="addUser">确 定</el-button>
    </div>
  </el-dialog>
</el-col>

添加数据及方法

js

data() {
    return {
      dialogFormVisible: false,
      form: {
        username: '',
        password:'',
        email:'',
        mobile:''

      },

……

methods 方法

 // 添加用户
    addUser(){
      this.$myHttp({
        url:'users',
        method:'post',
        // post数据提交
        data:this.form,
        headers: { Authorization: window.localStorage.getItem("token") }
      }).then(res=>{
          let {data} = res;
          if(data.meta.status == 201){
              // 将数据更新到页面
              this.tableData.push(data.data);
              this.$message({message: "添加用户成功",type: "success"});
              // 关闭窗口
              this.dialogFormVisible = false
          }
      })
    },

2.8 修改用户信息

绑定表单事件,传入 scope.row 以显示现有用户数据,做表单读入展示

html

<template slot-scope="scope">
  <el-button
    type="primary"
    icon="el-icon-edit"
    size="mini"
    @click="editUserShow(scope.row)"
    plain
  ></el-button>
  <el-button type="primary" icon="el-icon-check" size="mini" plain></el-button>
  <el-button
    type="primary"
    icon="el-icon-delete"
    size="mini"
    @click="deleteUser(scope.row.id)"
    plain
  ></el-button>
</template>

添加修改用户信息的弹窗,并在弹窗表单中展示用户信息

html

<!-- 修改用户弹窗 -->
<el-dialog title="添加用户" :visible.sync="editUser">
  <el-form :model="edit">
    <el-form-item label="姓名" label-width="90px">
      <el-input disabled v-model="edit.username"></el-input>
    </el-form-item>
    <el-form-item label="邮箱" label-width="90px">
      <el-input v-model="edit.email"></el-input>
    </el-form-item>
    <el-form-item label="电话" label-width="90px">
      <el-input v-model="edit.mobile"></el-input>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
    <el-button @click="editUser = false">取 消</el-button>
    <el-button type="primary" @click="editUserPut">确 定</el-button>
  </div>
</el-dialog>

js

// 弹窗并显示用户数据 用于修改表单
editUserShow(users){
    this.editUser = true; // 弹窗
    this.edit = users; // 直接使用表单数据
},

// 修改用户信息 入库
editUserPut(){
    var id = this.edit.id;
    var email = this.edit.email;
    var mobile = this.edit.mobile;

    this.$myHttp({
        url: `users/${id}`,
        method: "put",
        data:{email,mobile},
        headers: { Authorization: window.localStorage.getItem("token") }
    }).then(res=>{
        // console.log(res);
        if(res.data.meta.status == 200){
            this.editUser = false; // 关闭窗口
            this.getUserData(); // 重新获取数据
            this.$message({message: "修改用户成功",type: "success"});
        }
    })
}

2.9 修改用户角色

Select 选择器->基础用法 下拉框

html

<!-- 分配角色弹窗 -->
<el-dialog title="分配角色" :visible.sync="showRole">
  <el-form :model="role">
    <el-form-item label="当前用户" label-width="90px">
      <el-input disabled v-model="role.username"></el-input>
    </el-form-item>
    <el-form-item label="活动区域">
      <el-select v-model="roleId" placeholder="请选择活动区域">
        <el-option
          v-for="item in roleList"
          :key="item.key"
          :label="item.roleName"
          :value="item.id"
        >
        </el-option>
      </el-select>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    { {roleId} }
    <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
    <el-button @click="showRole = false">取 消</el-button>
    <el-button type="primary" @click="roleUserPut">确 定</el-button>
  </div>
</el-dialog>

弹窗后,获取全部角色遍历到 el-option ,获取用户 id 及修改后的角色,请求接口即可;

第 3 章 权限管理

3.1 权限列表

添加路由及组件文件

js

import Rights from '@/components/rights/rights'

{path:'rights',name:'rights',component:Rights}

html

<template>
  <div>
    <el-table
      height="850"
      ref="singleTable"
      :data="tableData"
      highlight-current-row
      style="width: 100%"
    >
      <el-table-column type="index" width="50"> </el-table-column>
      <el-table-column property="authName" label="权限名称" width="120">
      </el-table-column>
      <el-table-column property="path" label="路径" width="120">
      </el-table-column>
      <el-table-column property="一级" label="层级"> </el-table-column>
    </el-table>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        tableData: []
      };
    },
    mounted() {
      this.getlist();
    },
    methods: {
      getlist() {
        this.$myHttp({
          url: "rights/list",
          method: "get",
          headers: { Authorization: window.localStorage.getItem("token") }
        }).then(backs => {
          // console.log(backs);
          this.tableData = backs.data.data;
        });
      }
    }
  };
</script>

<style>
  .el-main {
    line-height: 30px;
  }
</style>

只要在el-table元素中定义了height=500属性,即可实现固定表头的表格,而不需要额外的代码。

修改层级展示

html

<el-table-column property="level" label="层级">
  <template slot-scope="scope">
    <span v-if="scope.row.level==='0'">一级</span>
    <span v-else-if="scope.row.level==='1'">二级</span>
    <span v-if="scope.row.level==='2'">三级</span>
  </template>
</el-table-column>

3.2 角色列表

添加路由及组件

js

import Roles from '@/components/roles/roles'

{path:'roles',name:'roles',component:Roles},

html

<template>
  <el-table :data="tableData5" style="width: 100%">
    <!-- 折叠内容 -->
    <el-table-column type="expand">
      <template slot-scope="props">
        <el-form label-position="left" inline class="demo-table-expand">
          <el-form-item label="商品名称">
            <span>{ { prop s.row.name }}</span>
          </el-form-item>
          <el-form-item label="所属店铺">
            <span>{ { prop s.row.shop }}</span>
          </el-form-item>
        </el-form>
      </template>
    </el-table-column>

    <!-- 表头及折叠按钮 -->
    <el-table-column label="角色名称" prop="id"> </el-table-column>
    <el-table-column label="角色描述" prop="name"> </el-table-column>
    <el-table-column label="操作" prop="desc"> </el-table-column>
  </el-table>
</template>

<script>
  export default {
    data() {
      return {
        tableData5: [
          {
            id: "12987122",
            name: "好滋好味鸡蛋仔",
            category: "江浙小吃、小吃零食",
            desc: "荷兰优质淡奶,奶香浓而不腻",
            address: "上海市普陀区真北路",
            shop: "王小虎夫妻店",
            shopId: "10333"
          },
          {
            id: "12987123",
            name: "好滋好味鸡蛋仔",
            category: "江浙小吃、小吃零食",
            desc: "荷兰优质淡奶,奶香浓而不腻",
            address: "上海市普陀区真北路",
            shop: "王小虎夫妻店",
            shopId: "10333"
          }
        ]
      };
    }
  };
</script>

<style>
  .demo-table-expand {
    font-size: 0;
  }
  .demo-table-expand label {
    width: 90px;
    color: #99a9bf;
  }
  .demo-table-expand .el-form-item {
    margin-right: 0;
    margin-bottom: 0;
    width: 50%;
  }
  .el-main {
    line-height: 20px;
  }
</style>

html

<!-- 表头及折叠按钮 -->
<el-table-column label="角色名称" prop="roleName"> </el-table-column>
<el-table-column label="角色描述" prop="roleDesc"> </el-table-column>
<el-table-column label="操作" prop="desc">
  <template slot-scope="scope">
    <el-button
      type="primary"
      icon="el-icon-edit"
      size="mini"
      circle
    ></el-button>
    <el-button
      type="success"
      icon="el-icon-check"
      size="mini"
      circle
    ></el-button>
  </template>
</el-table-column>

js

  data() {
    return {
      roleList: []
    };
  },
  mounted() {
    this.getrolelist();
  },
  methods: {
    getrolelist() {
      this.$myHttp({
        url: "roles",
        method: "get"
      }).then(back => {
        this.roleList = back.data.data;
      });
    }
  }

Tag 标签->可移除标签

html

<!-- 折叠内容 -->
<el-table-column type="expand">
  <template slot-scope="props">
    <el-tag closable>可移除</el-tag>
  </template>
</el-table-column>

分析角色数据,children 为上级角色中的子级角色;

html

<!-- 折叠内容 -->
<el-table-column type="expand">
  <template slot-scope="scope">
    { {scope.ro w.children}}
    <!-- <el-tag closable>{ {scope.ro w.children}} </el-tag> -->
  </template>
</el-table-column>

html

<!-- 折叠内容 -->
<el-table-column type="expand">
  <template slot-scope="scope">
    <!-- Layout 布局 -->
    <el-row>
      <!-- 一级区域 -->
      <el-col :span="6">
        <!-- 一级内容展示 -->
        <el-tag closable>{ {scope.ro w.children[1].authName}} </el-tag> >
      </el-col>

      <el-col :span="18">
        <!-- 二级区域 -->
        <el-row>
          <el-col :span="6">
            <!-- 二级内容 -->
            <el-tag closable type="success"
              >{ {scope.ro w.children[0].children[0].authName}}
            </el-tag>
            >
          </el-col>
          <el-col :span="18">
            <!-- 三级内容 -->
            <el-tag closable type="warning"
              >{ {scope.ro
              w.children[1].children[0].children[0].authName}}</el-tag
            >
            <el-tag closable type="warning"
              >{ {scope.ro
              w.children[1].children[0].children[1].authName}}</el-tag
            >
            <el-tag closable type="warning"
              >{ {scope.ro
              w.children[1].children[0].children[2].authName}}</el-tag
            >
          </el-col>
        </el-row>
      </el-col>
    </el-row>
  </template>
</el-table-column>

循环遍历所有层级角色

html

  <!-- 折叠内容 -->
    <el-table-column type="expand">
      <template slot-scope="scope">
        <!-- Layout 布局 -->
        <el-row class="rowmargin" v-for="item1 in scope.row.children" :key="item1.id">
          <!-- 一级区域 -->
          <el-col :span="6">
            <!-- 一级内容展示 -->
            <el-tag closable>{ {item1.authNam e}} </el-tag> >
          </el-col>

          <el-col :span="18">
            <!-- 二级区域 -->
            <el-row  v-for="item2 in item1.children" :key="item2.id">
              <el-col :span="6">
                  <!-- 二级内容 -->
                  <el-tag closable type="success">{ {item2.authNam e}} </el-tag> >
              </el-col>
              <el-col :span="18">
                  <!-- 三级内容 -->
                  <el-tag v-for="item3 in item2.children" :key="item3.id" closable type="warning">{ {item3.authNam e}} </el-tag>
              </el-col>
            </el-row>
          </el-col>
        </el-row>

        <!-- 判断没有权限 -->
        <el-row v-if="scope.row.children.length==0">
          <template><el-tag type="danger">木有权限</el-tag></template>
        </el-row>

      </template>
    </el-table-column>


……

.el-tag{
  margin-top: 10px;
  margin-right:5px;
}
<style>

3.3 删除角色权限

绑定 close 事件

页面元素删除

html

<!-- 三级内容 -->
<el-tag
  @close="closeTag(item2,key3)"
  v-for="(item3,key3) in item2.children"
  :key="item3.id"
  closable
  type="warning"
  >{ {item3.authNam e}}
</el-tag>

js

// 删除角色权限
closeTag(item,key){
    // 数组引用传递,直接删除即可
    // console.log(item,key)
    item.children.splice(key,1);
}

服务器删除

html

<el-col :span="18">
  <!-- 三级内容 -->
  <el-tag
    @close="closeTag(item2,key3,scope.row.id,item3.id)"
    v-for="(item3,key3) in item2.children"
    :key="item3.id"
    closable
    type="warning"
    >{ {item3.authNam e}}
  </el-tag>
</el-col>

js

// 删除角色权限
closeTag(item,key,roleId,rightId){
    // item 要删除元素所在父级数组
    // key 要删除元素所在父级数组下标
    item.children.splice(key,1);

    // roleid 角色ID,rightId权限ID
    // console.log(roleId,rightId);
    this.$myHttp({
        url:`roles/${roleId}/rights/${rightId}`,
        method:'delete'
    }).then(back=>{
        let {meta}  = back.data;
        // console.log(meta);
        if(meta.status == 200){
            this.$message({message:meta.msg,type:'success'});
        }
    })
}

3.4 修改角色权限

展示面板:

html

<template slot-scope="scope">
  <el-button type="primary" icon="el-icon-edit" size="mini" circle></el-button>
  <el-button
    type="success"
    icon="el-icon-check"
    size="mini"
    @click="rightsShow"
    circle
  ></el-button>
</template>

html

<!-- 修改角色授权面板 -->
<el-dialog title="修改角色权限" :visible.sync="isrightsShow">
  <div slot="footer" class="dialog-footer">
    <el-tree
      show-checkbox="true"
      :data="rightsList"
      :props="defaultProps"
    ></el-tree>
    <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
    <el-button @click="isrightsShow = false">取 消</el-button>
    <el-button type="primary" @click="rightsPut">确 定</el-button>
  </div>
</el-dialog>

js

return {
    // 所有权限列表
    rightsList:[],
    // 设置展示内容
    defaultProps: {
        children: 'children',
        label: 'authName'
    },

js

// 展示修改角色权限面板
rightsShow() {
    // 获取所有角色权限
    this.$myHttp({
        url:'rights/tree',
        method:'get'
    }).then(back=>{
        let {data,meta} = back.data;
        this.rightsList= data;
    })
    this.isrightsShow = true;
},

选中角色拥有的权限:

在点击按钮式,将所有角色的所有信息传入展示面板事件中:

html

<template slot-scope="scope">
  <el-button type="primary" icon="el-icon-edit" size="mini" circle></el-button>
  <el-button
    @click="rightsShow(scope.row)"
    type="success"
    icon="el-icon-check"
    size="mini"
    circle
  ></el-button>
</template>

html

<!-- 修改角色授权面板 -->
<el-dialog title="修改角色权限" :visible.sync="isrightsShow">
  <div slot="footer" class="dialog-footer">
    <!-- 
          default-expand-all 默认展开所有节点
          node-key="id" 将id设置为节点的唯一主键
          :default-checked-keys=[] 被选中主键的数组
          :props="defaultProps" 设置显示的内容            
          show-checkbox 节点可被选中
          -->
    <el-tree
      default-expand-all
      node-key="id"
      :default-checked-keys="defaultChecked"
      show-checkbox
      :data="rightsList"
      :props="defaultProps"
    ></el-tree>
    <!-- 点击取消或确定修改dialogFormVisible=false关闭窗口 -->
    <el-button @click="isrightsShow = false">取 消</el-button>
    <el-button type="primary" @click="rightsPut">确 定</el-button>
  </div>
</el-dialog>

js

data() {
    return {
      // 所有权限列表
      rightsList: [],
      // 设置展示内容
      defaultProps: {
        children: "children",
        label: "authName"
      },
      // 默认选中的节点数组
      defaultChecked: [],

      // 控制角色权限面板
      isrightsShow: false,

      // 所有角色数据列表
      roleList: []
    };
  },

……

// 展示修改角色权限面板
      rightsShow(row) {
          // 获取所有角色权限
          this.$myHttp({
              url: "rights/tree",
              method: "get"
          }).then(back => {
              let { data, meta } = back.data;
              // 显示所有权限
              this.rightsList = data;
          });

          // 遍历row,获取当前角色选中的所有权限,写入数组
          this.defaultChecked = [];
          // 在遍历赋值前,先清空数据,以免受其他数据影响
          var rr = row.children;
          rr.forEach(item1 => {
              item1.children.forEach(item2=>{
                  item2.children.forEach(item3=>{
                      // 只获取第三季选中即可
                      this.defaultChecked.push(item3.id);
                  })
              });
          });

          console.log(this.defaultChecked);
          // 控制显示窗口
          this.isrightsShow = true;
      },

提交数据入库:

js

    // 提交修改角色权限
    rightsPut() {
      // 在树形控件 中添加 ref="tree" 的属性,在此使用
      // elUI 中提供两个方法getCheckedKeys、getHalfCheckedKeys
      // 获取已选中的节点key
      var arr1 = this.$refs.tree.getCheckedKeys();
      var arr2 = this.$refs.tree.getHalfCheckedKeys();
      // concat() 合并两个数组的元素
      // join() 将数组的值以逗号隔开转为字符串
      var checkedKeys = arr1.concat(arr2).join();
      this.$myHttp({
        // 点击打开窗口是,保存角色id,在此获取使用
        url:`roles/${this.roleId}/rights`,
        method:'post',
        data:{rids:checkedKeys}
      }).then(back=>{
        let {data,meta} = back.data;
        if(meta.status == 200){
          this.isrightsShow = false; // 关闭窗口
          this.getrolelist(); // 刷新数据
          this.$message({message:meta.msg,type:'success'}); // 提示成功
        }
      })
    },

3.5 权限限制

对角色分配了权限后,我们并没有做限制,其实接口文档中左侧菜单权限 已经提供了相应的接口:

src/components/home/home.vue

html

<el-menu unique-opened :router="true" class="el-menu-vertical-demo">
  <el-submenu
    v-for="item in menusList"
    :key="item.id"
    :index="item.id.toString()"
  >
    <template slot="title">
      <i class="el-icon-location"></i>
      <span>{ {item.authNam e}} { {item.i d}}</span>
    </template>
    <el-menu-item
      v-for="item2 in item.children"
      :key="item2.id"
      :index="item2.path"
    >
      <i class="el-icon-menu"></i>
      { {item2.authNam e}} { {item2.pat h}}
    </el-menu-item>
  </el-submenu>
</el-menu>

<script>
  export default {
    // 使用生命周期的钩子函数,判断token
    mounted() {
      // 获取token
      var token = window.localStorage.getItem("token");
      if (!token) {
        // 错误提示
        this.$message.error("请登录");
        // 跳转到登录页面
        this.$router.push({ name: "Login" });
      } else {
        // 登录后,获取左侧菜单权限
        this.$myHttp({
          url: "menus",
          method: "get"
        }).then(back => {
          let { data, meta } = back.data;
          if (meta.status == 200) {
            console.log(data);
            this.menusList = data;
          }
        });
      }
    },

    data() {
      return {
        menusList: [],
        msg: "we"
      };
    },
    methods: {
      loginOut() {
        window.localStorage.removeItem("token");
        this.$message({
          message: "您已经退出,继续操作请重新登录",
          type: "success"
        });
        this.$router.push({ name: "Login" });
      }
    }
  };
</script>

3.6 导航守卫

导航守卫: https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

js

var router = new Router({……})

// 配置路由的导航守卫
router.beforeEach((to, from, next) => {
  // 如果访问登录的路由地址,放过
  if (to.name === 'Login') {
    next();
  } else {
    // 如果请求的不是登录页面,验证token
    // 1. 获取本地存储中的token
    const token = localStorage.getItem('token');
    if (!token) {
      // 2. 如果没有token,跳转到登录
      next({
        name: 'Login'
      });
    } else {
      // 3. 如果有token,继续往下执行
      next();
    }
  }
});

export default router;

第 99 章 项目打包及加载优化

打包命令:npm run build

打包完成后,直接将 dist 文件夹内容复制到服务器根目录即可;

我们的项目是很多组件组成的页面,但是,每次发送请求不管请求的是哪个路由的那个组件,很明显的都会将所有内容一次性全部加载出来,影响网站加载速度;如果我们可以在用户请求不同路由时,根据请求加载不同的页面,就会很大程度上提高页面的加载速度;

路由懒加载: https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

路由懒加载的工作就是在打包时,将路由文件分离出来,在请求时,需要哪个路由,再去请求相关文件;

用法:将路由引入的组件分别打包到不同的 js 文件;

打包完成后,很明显的在 JS 文件夹中多了一个 js 文件;

然后我们可以将所有的组件全部改为路由懒加载模式:

js

const Login = () => import("@/components/login/login");
const Home = () => import("@/components/home/home");
const UserList = () => import("@/components/userlist/user-list");
const RoleList = () => import("@/components/rolelist/role-list");
const RightsList = () => import("@/components/rightslist/rights-list");
const GoodsList = () => import("@/components/goodslist/goods-list");
const GoodsCategories = () =>
  import("@/components/goodscategories/goods-categories");
const GoodsAdd = () => import("@/components/goodsadd/goods-add");
const Report = () => import("@/components/report/report");
const Order = () => import("@/components/orders/orders");
const Params = () => import("@/components/params/params");

但这是不够的,我们知道,很多组件都是可以用 CDN 加载的;

1:找到 cdn 地址,直接在 index.html 中加入地址,注意,cdn 引入版本要和项目中的版本保持一致;

html

<body>
  <div id="app"></div>
  <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
  <!-- built files will be auto injected -->
</body>

2:修改 webpack 配置文件 https://www.webpackjs.com/configuration/externals/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ES6新特性

    由于ES6在一些低版本的浏览器上无法运行,需转成ES5之前的版本兼容,以下有几种方案可以自动转换

    jinghong
  • Node

    想要实现其他复杂的操作和效果,都要依靠 宿主环境 提供API,目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是 浏览器 和 操作系统 ;

    jinghong
  • Canvas

    http://www.w3c.org/TR/2dcontext/ https://html.spec.whatwg.org/

    jinghong
  • vue入门笔记

    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。

    Noneplus
  • vue-print-nb 打印插件的使用

    import Print from 'vue-print-nb' Vue.use(Print)

    tianyawhl
  • 全局变量的三种声明方法

    显示true FWK未定义,说明隐式声明的全局变量可以被delete,与global对象的parseInt、escape、parseFloat等等类似,可直接被...

    meteoric
  • 受控组件表单&不受控组件

    表单里面的数据 根据State确定 在 HTML 中,表单元素如 <input>,<textarea> 和 <select>表单元素通常保持自己的状态,并根据...

    河湾欢儿
  • Leetcode 264. 丑数 II

    解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

    zhipingChen
  • 诚邀:每日十万+提问,知乎精准推荐如何做得更好?

    1 月 10 日,北京智源人工智能研究院联合知乎、数据评测平台biendata举办的 “2019智源·知乎看山杯专家发现算法大赛”正式收官。该比赛从2019年9...

    AI科技大本营
  • JavaScript设计模式--模板方法模式

    模板方法是基于继承的设计模式,可以很好的提高系统的扩展性。 java中的抽象父类、子类 模板方法有两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子...

    奋飛

扫码关注云+社区

领取腾讯云代金券