第 0 章 Vue 介绍
0.0 开发工程发展历史
通过前面的介绍,我们对目前的项目工程化有了大体了了解,那么其中,在第二阶段的工程化演进中,有一个重要的工程设计理念诞生,他就是著名的 MVC 设计模式,简单点,MVC 其实就是为了项目工程化的一种分工模式;
MVC 中的最大缺点就是单项输入输出,所有的 M 的变化及 V 层的变化,必须通过 C 层调用才能展示;
为了解决相应的问题,出现了 MVVM 的设计思想,简单理解就是实想数据层与展示层的相互调用,降低业务层面的交互逻辑;后面再进行详细介绍;
0.1 Vue 介绍
Vue (读音 /vjuː/,类似于 view ) 是一套用于构建用户界面的 渐进式框架 。
注意:Vue 是一个框架,相对于 jq 库来说,是由本质区别的;
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/[email protected]/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} }
</body>
// 两种引入方式,任意选择
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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} }
</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} }
</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 语句;
(表达式是运算,有结果;语句就是代码,可以没有结果)
<div id="div">
{ { u n > 3 ? '大' : '小'}} { { fu n() }}
</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>
</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>
</body>
<script>
var app = new Vue({
el: "#div",
data: {
seen: "<h1>Vue</h1>",
str_html: "<h1>Vue</h1>",
class: "dd"
</script>
注意:
-
v-text
-
v-text 和差值表达式的区别
- v-text 标签的指令更新整个标签中的内容(替换整个标签包括标签自身)
- 差值表达式,可以更新标签中局部的内容
-
v-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>
<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>
<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>
<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" />
<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" />
<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" />
<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>
<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 }}
<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"
{ {sex} }
<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} }
<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" />
<script>
var vm = new Vue({
el: "#app",
data: {
cli: function() {
alert("123");
</script>
上面的代码运行是没有问题的,但是,我们不建议这样做,因为 data 是专门提供数据的对象,事件触发需要执行的是一段代码,需要的是一个方法 (事件处理程序) ;
修改代码如下:
html
<div id="app">
<!-- 使用事件绑定的简写形式 -->
<input type="button" value="按钮" @click="cli" />
<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)" />
<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>
<script>
document.getElementById("a").onclick = ev => {
// 组织浏览器的默认行为
ev.preventDefault();
</script>
使用修饰符 阻止浏览器的默认行为
html
<div id="app">
<a href="http://www.qq.com" @click.prevent="cli">腾百万</a>
<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>
<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" />
<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" />
<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" />
<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>
<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>
<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'">
<div v-else-if="type === 'B'">
<div v-else-if="type === 'C'">
<div v-else>
Not A/B/C
<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">
<li v-for="(val,key) in arr">{ {val}}--- { {key}}< /li></li>
<li v-for="(val,key) in obj">
{ {val}}--- { {key}}< /li> // in 也可以用 of 来替换 如: (val,key)of
obj,两者没有区别
<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>
<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>
<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>
<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 案例
为什么选择这样的案例:
产品功能简洁,需求明确,所需知识点丰富;实现基本功能容易,涵盖所学基础知识;而可扩展性强,完善所有功能比较复杂,所需技术众多;在学习中,可以灵活取舍;
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>
<input class="edit" value="Rule the web" />
</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}}
<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() }}
<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" />
{ {fulln} }
<!-- 一百次调用 -->
{ {fulln} }
<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 }}
<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 }}
<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>
<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}}
<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}}
<!-- 定义两个DOM节点 -->
<div id="app2">
<input type="text" v-model="msg" /> <br />
{ {msg|myFilter s|get3}}
<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>
<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>
<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>
<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>
<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>
</transition-group>
<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,默认为span
-
move-class
- 覆盖移动过渡期间应用的 CSS 类。 -
除了
mode
,其他特性和<transition>
相同。
初次渲染动画
js
//在transition标签中,可以设置一个appear指令,这个指令可以实现打开网页\初次进入时触发动画
//基本使用:
<transition appear ></transition>
//也可使用自定义动画效果或第三方库,通过修改class实现
<transition appear appear-active-class='animated swing' ></transition>
//appear也可修改类名
vue2-animate 动画样式参考链接: https://the-allstars.com/vue2-animate/
animate 样式参考链接: https://daneden.github.io/animate.css/
官方文档所说:
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的
<transition>
,则
v-
是这些类名的默认前缀。如果你使用了
<transition name="my-transition">
,那么
v-enter
会替换为
my-transition-enter
。
这就是 Vue 中动画及过渡的基本使用方式,因为这些动画效果都需要我们自己写 CSS 样式,相对比较麻烦,在项目中,大多情况下,我们会借助第三方 CSS 动画库来实现,如:Animate.css ;后面项目中具体使用时,我们在进一步学习第三方 CSS 动画库的使用;
使用 js 钩子来动画
html
<transition <!-- 进入 -->
v-on:before-enter="beforeEnter" v-on:enter="enter"
v-on:after-enter="afterEnter" v-on:enter-cancelled="enterCancelled"
<!-- 离开 -->
v-on:before-leave="beforeLeave" v-on:leave="leave"
v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" >
<!-- ... -->
</transition>
// ... methods: { // -------- // 进入中 // -------- beforeEnter: function (el) {
// ... }, // 当与 CSS 结合使用时 // 回调函数 done 是可选的 enter: function (el,
done) { // ... done() }, afterEnter: function (el) { // ... }, enterCancelled:
function (el) { // ... }, // -------- // 离开时 // -------- beforeLeave:
function (el) { // ... }, // 当与 CSS 结合使用时 // 回调函数 done 是可选的
leave: function (el, done) { // ... done() }, afterLeave: function (el) { // ...
}, // leaveCancelled 只用于 v-show 中 leaveCancelled: function (el) { // ... } }
@before-enter 动画执行前
@enter 执行动画
@after 动画执行中
@enter 执行完毕
//离开/进入 各有一套
-->
当只用 JavaScript 过渡的时候, 在 enter 和 leave 中必须使用 done 进行回调 。否则,它们将被同步调用,过渡会立即完成。
推荐对于仅使用 JavaScript 过渡的元素添加
v-bind:css="false"
,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
Velocity.js + js 钩子
velocity 下载链接: http://www.velocityjs.org/
https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90
html
<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
<p v-if="show">
</transition>
<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(
{ translateX: "15px", rotateZ: "50deg" },
{ duration: 600 }
Velocity(el, { rotateZ: "100deg" }, { loop: 2 });
Velocity(
rotateZ: "45deg",
translateY: "30px",
translateX: "30px",
opacity: 0
{ complete: done }
</script>
多个元素/组件过渡
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
Resources
http://localhost:3000/tb1
http://localhost:3000/tb2
http://localhost:3000/tb3
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>
<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 id="app2">
<!-- 不可用 -->
<mytemp></mytemp>
<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 id="app2">
<!-- 不可用 -->
<mytemp></mytemp>
<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>
<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>
<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} }
<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} }
<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>
<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>
<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>
<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>
<!-- 子组件 -->
<template id="app2">
<h1>我是子组件一!</h1>
</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来获取发来的值
<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>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>
<template id="child">
<slot name='header'>Default</slot>
<slot name='center'>Default</slot>
<slot name='footer'>Default</slot>
</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>
<template id="container">
<header>
<slot name="header"></slot>
</header>
<slot name="body"></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</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>
<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>
<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>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令-->
<li v-for="i in list" v-once>{ {i}}< /li></li>
//试着不要过度使用这个模式。当你需要渲染大量静态内容时,极少数的情况下它会给你带来便利,除非你非常留意渲染变慢了,不然它完全是没有必要的——再加上它在后期会带来很多困惑。例如,设想另一个开发者并不熟悉
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>
树内的所有嵌套组件中触发。 主要用于保留组件状态或避免重新渲染。<!-- 基本 -->
<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
属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:<!-- 逗号分隔字符串 -->
<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 新增
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
<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: {
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" />
<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
$route 当前路由信息 $router 操作路由
html
<!-- 引入路由 -->
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<div id="app">
<li><a href="#/login">登录</a></li>
<li><a href="#/register">注册</a></li>
<!-- 路由中设置的组件会替换router-view标签 -->
<router-view></router-view>
<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">
<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属性
<!-- 路由中设置的组件会替换router-view标签 -->
<router-view></router-view>
</div>
使用 router-link 的一大好处就是,每当我们点击时,在标签内就会自动帮我们添加 class 属性,而此时,我们就可以利用 class 属性,来定义样式:
html
<style>
.router-link-active {
color: red;
</style>
11.3 动态路由匹配
假设有一个用户列表,想要删除某一个用户,需要获取用户的 id 传入组件内,如何实现呢?
此时可以通过路由传参来实现,具体步骤如下:
-
通过传参,在路径上传入具体的值
<router-link
to="/users/120">用户管理</router-link>
-
路由规则中增加参数,在 path 最后增加
:id
{ name:
'users', path:
'/users/:id', component: Users },
-
在组件内部可以使用,
this.$route
获取当前路由对象
var Users =
{ template:
"<div>这是用户管理内容 { { $rout e.params.id }}</div>",
mounted()
{ console.log(this.$route.params.id);
}
};
第 12 章 构建一个项目
12.0 命令行工具 (CLI)
https://cn.vuejs.org/v2/guide/installation.html#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7-CLI
Vue 提供了一个 官方的 CLI ,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了 batteries-included 的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。更多详情可查阅 Vue CLI 的文档 。
12.1 初始化项目
安装 cli 命令工具:
npm install -g @vue/cli @vue/cli-init
安装成功后,使用
vue -V
命令,查看版本号;
使用
vue init webpack myapp
构建一个名为 myapp 的项目:
Vue 依然使用询问的方式,让我们对项目有一个初始化的信息
- Project name:项目名
- Project description: 项目描述
- Author: 作者
-
Vue build:
- 第一种:配合大部分的开发人员
- 第二种:仅仅中有 runtime
- Install vue-router? 是否安装 vue-router
- Use ESLint to lint your code?是否使用 ESLint 来验证我们的语法。
-
Pick an ESLint preser:使用哪种语法规范来检查我们的代码:
- Standard: 标准规范
- Airbnb: 爱彼迎规范
- Set up unit test: 设置单元测试
- Setup e2e tests: 设置端对端测试
-
Should we run ‘npm install’:要不要帮忙你下载这个项目需要的第三方包
- 使用 npm 来下载
- 使用 yarn 来下载
shell
To get started:
cd myapps
npm run dev // 使用命令启动项目
-----
Your application is running here: http://localhost:8080
打开浏览器,访问 http://localhost:8080
看到浏览器的欢迎界面,表示项目运行成功
12.2 项目结构介绍
├── build webpack打包相关配置文件目录
├── config webpack打包相关配置文件目录
├── node_modules 第三方包
├── src 项目源码(主战场)
│ ├── assets 存储静态资源,例如 css、img、fonts
│ ├── components 存储所有公共组件
│ ├── router 路由
│ ├── App.vue 单页面应用程序的根组件
│ └── main.js 程序入口,负责把根组件替换到根节点
├── static 可以放一些静态资源
│ └── .gitkeep git提交的时候空文件夹不会提交,这个文件可以让空文件夹可以提交
├── .babelrc 配置文件,es6转es5配置文件,给 babel 编译器用的
├── .editorconfig 给编辑器看的
├── .eslintignore 给eslint代码风格校验工具使用的,用来配置忽略代码风格校验的文件或是目录
├── .eslintrc.js 给eslint代码风格校验工具使用的,用来配置代码风格校验规则
├── .gitignore 给git使用的,用来配置忽略上传的文件
├── index.html 单页面应用程序的单页
├── package.json 项目说明,用来保存依赖项等信息
├── package-lock.json 锁定第三方包的版本,以及保存包的下载地址
├── .postcssrc.js 给postcss用的,postcss类似于 less、sass 预处理器
└── README.md 项目说明文档
12.3 语法检查
注意 :如果我们在 构建项目时 选择了
Use ESLint to lint your code
那么我们在写代码时必须严格遵守
JavaScript Standard Style
代码风格的语法规则:
- 使用两个空格 – 进行缩进
- 字符串使用单引号 – 需要转义的地方除外
- 不再有冗余的变量 – 这是导致 大量 bug 的源头!
- 无分号 – 这 没什么不好。 不骗你!
-
行首不要以
(
,[
, or ``` 开头- 这是省略分号时 唯一 会造成问题的地方 – 工具里已加了自动检测!
- 详情
-
关键字后加空格
if (condition) { ... }
-
函数名后加空格
function name (arg) { ... }
-
坚持使用全等
===
摒弃==
一但在需要检查null || undefined
时可以使用obj == null
。 -
一定要处理 Node.js 中错误回调传递进来的
err
参数。 -
使用浏览器全局变量时加上
window
前缀 –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} }
</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>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)" />
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')">
<input type="button" value="张三出现" @click="setOnline(5)" />
<li v-for="user in onlineUsers">
名字:{ {user.nam e}}
年龄:{ {user.ag e}}
</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>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</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>
</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="" />
</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 导航栏中需要展示的内容
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>
<script>
// 1:定义路由组件
var login = {
template: `
<h2>我是登录页面</h2>
<li><router-link to="/zi">子路由</router-link></li>
<!--路由组件中继续使用路由组件-->
<router-view></router-view>
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>
<!-- 面包鞋 -->
<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>
</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>
<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>
</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>
</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>
</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>
</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>
</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>
</el-dialog>
弹窗后,获取全部角色遍历到
el-option
,获取用户 id 及修改后的角色,请求接口即可;
第 3 章 权限管理
3.1 权限列表
添加路由及组件文件
js
import Rights from '@/components/rights/rights'
{path:'rights',name:'rights',component:Rights}
html
<template>
<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>
</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>
</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>
</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");