您的位置 首页 > 腾讯云社区

Vue---jinghong

第 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-textv-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 有几个好处:

扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。当一个 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 编译时改变成指定的组件

不受这种影响的情况有:

字符串 (例如:template: '...')单文件组件 (.vue)

js

<!-- 当 `currentView` 改变时,组件也跟着改变 --> <component v-bind:is="currentView"></component> <!-- 这样做是有必要的,因为 `<my-row>` 放在一个 --> <!-- `<table>` 内可能无效且被放置到外面 --> <table> <tr is="my-row"></tr> </table> 第 4 章 TodoList 案例

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

学习练手项目 : TodoMVC 、 Vue 官方示例

为什么选择这样的案例:

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

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 完成任务

如果任务完成,状态改为选中, li 的 class 属性为 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 全局过滤器

上面的代码中,myFilters 及 get3 两个过滤器,仅在当前 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 切换。

v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to: 2.1.8 版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。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 会等待过渡所在根元素的第一个 transitionend 或 animationend 事件。

transition-group

tag - string,默认为 spanmove-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 实例对象接收相同的参数选项 data、computed、watch、methods , 但 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> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。 在 2.2.0 及其更高版本中,activated 和 deactivated 将会在 <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 新增 include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示: 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"

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted、updated 和 destroyed。生命周期钩子的 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 传入组件内,如何实现呢?

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

通过传参,在路径上传入具体的值 html <router-link to="/users/120">用户管理</router-link>路由规则中增加参数,在 path 最后增加 :id js { name: 'users', path: '/users/:id', component: Users },在组件内部可以使用,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:第一种:配合大部分的开发人员第二种:仅仅中有 runtimeInstall vue-router? 是否安装 vue-routerUse 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 前缀 – document 和 navigator 除外避免无意中使用到了这些命名看上去很普通的全局变量, 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不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化不能使用arguments.callee不能使用arguments.caller禁止this指向全局对象不能使用fn.caller和fn.arguments获取函数调用的堆栈增加了保留字(比如protected、static和interface)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.css : Form 表单

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/

---来自腾讯云社区的---jinghong

关于作者: 瞎采新闻

这里可以显示个人介绍!这里可以显示个人介绍!

热门文章

留言与评论(共有 0 条评论)
   
验证码: