首页
学习
活动
专区
工具
TVP
发布

Vue

作者头像
jinghong
发布2020-05-12 17:50:02
6K0
发布2020-05-12 17:50:02
举报
文章被收录于专栏:前端开发0202前端开发0202

第 0 章 Vue 介绍

0.0 开发工程发展历史

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

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

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

0.1 Vue 介绍

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

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

https://cn.vuejs.org/

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

0.2 Vue 初体验

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

CDN 引入:

js

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

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

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

html

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

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

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

0.3 学习 Vue

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

第 1 章 Vue 实例对象

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

js

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

html

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

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

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

js

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

console.log(app.user_name);

第 2 章 模板语法-插值

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

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

2.1 文本

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

html

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

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

html

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

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

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

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

html

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

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

第 3 章 模板语法-指令

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

html

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

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

3.1 v-text / v-html 文本

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

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

html

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

注意:

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

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

3.2 v-bind 属性绑定

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

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

动态绑定图片的路径

html

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

绑定 a 标签上的 id

html

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

绑定 class

对象语法和数组语法

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

绑定 style

对象语法和数组语法

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

html

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

简化语法

html

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

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

3.3 v-model 双向数据绑定

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

单向数据绑定

html

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

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

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

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

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

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

双向数据绑定

html

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

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

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

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

多行文本 / 文本域

html

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

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

绑定复选框

html

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

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

绑定单选框

html

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

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

修饰符

.lazy - 取代 input 监听 change 事件

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

.trim - 输入首尾空格过滤

html

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

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

3.4 v-on 绑定事件监听

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

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

3.4.1 基本使用

html

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

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

修改代码如下:

html

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

向事件处理器中传参

html

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

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

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

js

methods: {
    // 接受参数
    cli: function (a,b,ev) {
        alert(a+b);
        console.log(ev);
    }
}
3.4.2 事件修饰符

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

html

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

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

html

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

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

html

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

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

html

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

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

html

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

鼠标中键触发事件

html

<div id="app">
  <input type="button" value="按钮" @click.middle="cli" />
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      cli: () => {
        console.log("111");
      }
    }
  });
</script>
3.4.6 为什么在 HTML 中监听事件?

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

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

3.5 v-show 显示隐藏

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

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

html

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

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

html

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

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

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

html

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

3.7 v-for 循环

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

html

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

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

3.8 v-cloak

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

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

html

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

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

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

html

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

3.9 v-once

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

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

html

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

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

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

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

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

数组监测

js

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

js

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

js

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

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

补充:is

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

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

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

不受这种影响的情况有:

js

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

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

第 4 章 TodoList 案例

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

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

为什么选择这样的案例:

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

4.1 项目初始化

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

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

4.2 数据遍历

js

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

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

html

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

4.3 展示无数据状态

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

html

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

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

html

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

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

html

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

4.3 添加任务

绑定 enter 键盘事件:

html

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

js

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

修改代码完成任务添加:

js

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

4.4 任务的全选与反选

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

为元素绑定点击事件:

html

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

添加处理程序:

js

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

4.5 完成任务

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

html

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

4.6 删除任务

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

html

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

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

js

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

4.7 删除已完成的任务

绑定事件

html

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

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

js

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

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

js

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

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

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

console.log(new_arr);

修改项目代码:

js

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

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

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

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

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

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

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

第 5 章 MVVM 设计思想

MVC 设计思想:

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

V: Views 视图层 渲染数据

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

MVVM 设计思想:

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

V: Views 视图层 渲染数据

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

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

第 6 章 其他知识点汇总

6.1 计算属性与侦听器

6.1.1 计算属性

html

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

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

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

html

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

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

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

html

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

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

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

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

html

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

js

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

html

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

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

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

html

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

6.2 使用 ref 操作 DOM

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

html

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

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

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

6.3 过滤器的使用

6.3.1 私有(局部)过滤器

定义过滤器

js

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

过滤器的使用:

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

{ {msg|myFilter s}}

过滤敏感词汇

html

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

        return val.replace("苍井空", "***");
      }
    }
  });
</script>
6.3.2 全局过滤器

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

Vue.filter(名称,处理器)

html

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

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

6.4 自定义指令

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

6.4.1 全局自定义指令

js

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

html

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

html

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

js

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

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

html

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

6.5 过度及动画

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

html

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

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

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

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

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

html

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

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

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

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

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

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

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

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

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

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

transition

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

transition-group

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

js

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

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

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

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

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

使用 js 钩子来动画

html

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

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

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

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

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

Velocity.js + js 钩子

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

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

html

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

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

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

html

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

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

第 7 章 json-server 与 axios

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

7.1 json-server 使用

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

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

json

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

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

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

shell

$ json-server --watch data.json

  \{^_^}/ hi!

  Loading data.json
  Done

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

  Home
  http://localhost:3000

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

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

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

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

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

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

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

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

7.2 axios

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

html

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

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

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

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

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

第 8 章 重构 TodoList 案例

8.1 启动 API 接口及数据

db.json:

json

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

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

8.2 获取全部任务

js

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

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

8.3 添加任务

js

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

    ……

8.4 删除任务

html

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

js

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

8.5 完成任务

html

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

js

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

8.6 案例中的 Bug

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

第 9 章 组件

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

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

9.1 认识组件

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

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

9.2 基本使用

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

html

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

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

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

这种组件被称为 全局组件

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

html

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

9.3 使用注意

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

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

html

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

9.4 组件的使用

CSS 代码

css

* {
  margin: 0;
  padding: 0;
}

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

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

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

原始 HTML 代码

html

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

组件化代码

html

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

9.5 组件中的数据及方法

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

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

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

html

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

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

9.6 vue 实例也是组件

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

html

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

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

html

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

父子组件通信*

通过 Prop 向子组件传递数据

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

html

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

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

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

html

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

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

html

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

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

检索 prop 数据类型

js

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

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

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

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

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

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

js

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

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

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

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

js

<div id="app" >
        //3.在父组件中绑定传过来的自定义事件,然后使用这个自定义事件绑定自己的函数,即可实现子传父
        <mytemp @childevents="Sendparent"> </mytemp>
        //<mytemp @childevents="msg = $event"> </mytemp> 也可以不用函数,使用$event来获取发来的值
</div>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          msg: "数据"
        },
        components: {
          mytemp: {
               //1.在子组件模板中,定义一个触发事件,触发的函数必须是子组件自己拥有的函数
            template: ' <input type="button" value="提交" @click="send" />',
            data() {
              return {
                chilAttr: "child"
              };
            },
            methods: {
              send() {
                //2.使用$emit实现子传父
                this.$emit("childevents", this.chilAttr);
                //向父元素发送自定义事件    两个参数:1 自定义事件名   2 传参
              }
            }
          }
        },
        methods: {
          Sendparent(child) {
              this.msg = child;
          }
        }
      });
    </script>
//注意: 自定义事件的命名不能为驼峰,否则会出错。
父子之间访问
vm.$parent
  • 类型Vue instance
  • 只读
  • 详细: 父实例,如果当前实例有的话。
vm.$root
  • 类型Vue instance
  • 只读
  • 详细: 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
vm.$children
  • 类型Array<Vue instance>
  • 只读
  • 详细: 当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

非父子组件传值

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

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

html

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

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

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

插槽

js

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

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

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

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

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

编译作用域

js

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

js

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


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

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

动态组件的使用

html

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

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

html

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

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

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

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

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

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

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

keep-live

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

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

第 10 章 Vue 的生命周期

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

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

js

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

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

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

html

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

第 11 章 单页应用

11.1 单页应用

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

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

11.2 vue 路由插件 vue-router

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

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

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

html

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

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

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

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

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

html

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

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

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

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

html

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

11.3 动态路由匹配

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

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

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

第 12 章 构建一个项目

12.0 命令行工具 (CLI)

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

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

12.1 初始化项目

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

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

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

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

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

shell

To get started:

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

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

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

12.2 项目结构介绍

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

12.3 语法检查

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

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

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

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

12.4 项目代码预览

12.4.1 知识储备
严格模式

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

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

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

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

总结:

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

main.js

js

// 入口文件

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

Vue.config.productionTip = false;

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

roter/index.js

js

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

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

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

components/HelloWorld.vue

js

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

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

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

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

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

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

12.5 添加自己的路由组件

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

js

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

Vue.use(Router);

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

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

html

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

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

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

浏览器渲染效果如下:

第 13 章 Vuex

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

严格模式

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

js

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

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

#开发环境与发布环境

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

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

js

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

基本使用/介绍

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

js

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

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

//基本使用/介绍

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

辅助方法

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

映射

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

js

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

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

基本使用(综合案例)

vuex 配置

js

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

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

Vue.use(Vuex);

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

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

调用 vuex

html

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

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

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

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

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

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

<style scoped>
</style>

Vue 前后端分离项目

第 0 章 项目如何开始的

0.1 总体流程

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

0.2 数据服务器构建

0.2.1 技术栈

Vue+elementUI+NodeJS+MySQL

0.2.2 数据服务器准备

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

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

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

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

0.3 接口测试

0.3.1 登录

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

0.3.2 获取用户信息

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

使用 token 替换 cookie 的功能

0.4 Vue 项目初始化

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

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

0.5 项目预览

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

第 1 章 开始项目

1.1 添加用户登录路由组件

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

js

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

Vue.use(Router);

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

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

html

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

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

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

1.2 使用 ElementUI

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

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

js

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

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

Vue.config.productionTip = false;

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

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

html

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

1.3 搭建登录页面

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

css

html,
body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
}

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

javascript

// 代码略...

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

// 代码略...

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

所以我们在 src/App.vue

css

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

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

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

html

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

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

css

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

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

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

1.4 完成登录功能

1.4.1 封装 axios

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

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

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

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

js

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

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

js

// 导入 myaxios 模块
import myaxios from "@/assets/js/myaxios.js";
Vue.use(myaxios); // 注册使用 axios 插件
1.4.2 完成登录功能

发送 post 请求

js

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

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

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

js

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

修改提示弹窗

js

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

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

js

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

js

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

1.5 首页

1.5.1 添加路由及页面布局

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

js

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

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

js

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

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

html

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

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

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

html

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

css

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

/src/components/home/home.vue

Layout 布局

html

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

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

/src/components/home/home.vue

NavMenu 导航菜单 Icon 图标

html

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

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

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

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

css

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

.el-submenu {
  text-align: left;
}
1.5.4 右侧内容

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

html

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

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

html

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

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

<style></style>

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

js

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

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

知识补充:

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

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

html

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

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

*再谈 代码加载流程 *

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

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

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

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

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

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

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

js

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

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

js

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

1.6 验证首页登录

src/components/login/login.vue

js

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

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

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

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

js

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

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

1.7 用户退出

绑定点击事件

html

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

js

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

第 2 章 用户管理

2.1 路由及组件

/src/components/home/home.vue

html

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

src/router/index.js

js

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

……

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

src/components/users/users.vue

html

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

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

<style></style>

2.2 面包屑导航及搜索框

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

html

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

html

……
</el-card>

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

</div>

……

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

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

2.3 展示用户列表

2.3.4 组件展示

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

html

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

</div>
</template>

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

.el-main{
  line-height:30px;
}
</style>
2.3.5 获取数据

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

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

js

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

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

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

2.3.6 操作按钮

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

html

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

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

html

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

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

html

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

js

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

Pagination 分页->附加功能

html


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

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

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

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

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

js

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

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

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

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

2.4 模糊搜索

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

html

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

……

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

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


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

2.5 切换用户状态

html

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

js

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

修改失败是因为没有 token:

js

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

2.6 删除用户

MessageBox 弹框->确认消息

js

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


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

2.7 添加用户

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

表单弹窗:

html

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

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

修改表单

html

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

添加数据及方法

js

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

      },

……

methods 方法

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

2.8 修改用户信息

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

html

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

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

html

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

js

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

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

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

2.9 修改用户角色

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

html

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

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

第 3 章 权限管理

3.1 权限列表

添加路由及组件文件

js

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

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

html

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

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

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

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

修改层级展示

html

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

3.2 角色列表

添加路由及组件

js

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

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

html

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

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

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

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

html

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

js

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

Tag 标签->可移除标签

html

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

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

html

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

html

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

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

循环遍历所有层级角色

html

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

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

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

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


……

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

3.3 删除角色权限

绑定 close 事件

页面元素删除

html

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

js

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

服务器删除

html

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

js

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

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

3.4 修改角色权限

展示面板:

html

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

html

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

js

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

js

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

选中角色拥有的权限:

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

html

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

html

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

js

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

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

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

……

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

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

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

提交数据入库:

js

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

3.5 权限限制

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

src/components/home/home.vue

html

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

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

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

3.6 导航守卫

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

js

var router = new Router({……})

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

export default router;

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

打包命令:npm run build

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

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

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

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

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

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

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

js

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

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

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

html

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 0 章 Vue 介绍
    • 0.0 开发工程发展历史
      • 0.1 Vue 介绍
        • 0.2 Vue 初体验
          • 0.3 学习 Vue
          • 第 1 章 Vue 实例对象
          • 第 2 章 模板语法-插值
            • 2.1 文本
            • 第 3 章 模板语法-指令
              • 3.1 v-text / v-html 文本
                • 3.2 v-bind 属性绑定
                  • 3.3 v-model 双向数据绑定
                    • 3.4 v-on 绑定事件监听
                      • 3.5 v-show 显示隐藏
                        • 3.6 v-if / v-else / v-else-if 条件判断
                          • 3.7 v-for 循环
                            • 3.8 v-cloak
                              • 3.9 v-once
                                • 补充:数组更新检测/对象更新检测
                                  • 补充:is
                                  • 第 4 章 TodoList 案例
                                    • 4.1 项目初始化
                                      • 4.2 数据遍历
                                        • 4.3 展示无数据状态
                                          • 4.3 添加任务
                                            • 4.4 任务的全选与反选
                                              • 4.5 完成任务
                                                • 4.6 删除任务
                                                  • 4.7 删除已完成的任务
                                                  • 第 5 章 MVVM 设计思想
                                                  • 第 6 章 其他知识点汇总
                                                    • 6.1 计算属性与侦听器
                                                      • 6.2 使用 ref 操作 DOM
                                                        • 6.3 过滤器的使用
                                                          • 6.4 自定义指令
                                                            • 6.5 过度及动画
                                                            • 第 7 章 json-server 与 axios
                                                              • 7.1 json-server 使用
                                                                • 7.2 axios
                                                                • 第 8 章 重构 TodoList 案例
                                                                  • 8.1 启动 API 接口及数据
                                                                    • 8.2 获取全部任务
                                                                      • 8.3 添加任务
                                                                        • 8.4 删除任务
                                                                          • 8.5 完成任务
                                                                            • 8.6 案例中的 Bug
                                                                            • 第 9 章 组件
                                                                              • 9.1 认识组件
                                                                                • 9.2 基本使用
                                                                                  • 9.3 使用注意
                                                                                    • 9.4 组件的使用
                                                                                      • 9.5 组件中的数据及方法
                                                                                        • 9.6 vue 实例也是组件
                                                                                          • 父子组件通信*
                                                                                            • 非父子组件传值
                                                                                              • 插槽
                                                                                                • 动态组件 & v-once & keep-live
                                                                                                • 第 10 章 Vue 的生命周期
                                                                                                • 第 11 章 单页应用
                                                                                                  • 11.1 单页应用
                                                                                                    • 11.2 vue 路由插件 vue-router
                                                                                                      • 11.3 动态路由匹配
                                                                                                      • 第 12 章 构建一个项目
                                                                                                        • 12.0 命令行工具 (CLI)
                                                                                                          • 12.1 初始化项目
                                                                                                            • 12.2 项目结构介绍
                                                                                                              • 12.3 语法检查
                                                                                                                • 12.4 项目代码预览
                                                                                                                  • 12.5 添加自己的路由组件
                                                                                                                  • 第 13 章 Vuex
                                                                                                                    • 严格模式
                                                                                                                      • #开发环境与发布环境
                                                                                                                        • 基本使用/介绍
                                                                                                                          • 辅助方法
                                                                                                                            • 基本使用(综合案例)
                                                                                                                            • Vue 前后端分离项目
                                                                                                                              • 第 0 章 项目如何开始的
                                                                                                                                • 0.1 总体流程
                                                                                                                                • 0.2 数据服务器构建
                                                                                                                                • 0.3 接口测试
                                                                                                                                • 0.4 Vue 项目初始化
                                                                                                                                • 0.5 项目预览
                                                                                                                              • 第 1 章 开始项目
                                                                                                                                • 1.1 添加用户登录路由组件
                                                                                                                                • 1.2 使用 ElementUI
                                                                                                                                • 1.3 搭建登录页面
                                                                                                                                • 1.4 完成登录功能
                                                                                                                                • 1.5 首页
                                                                                                                                • 1.6 验证首页登录
                                                                                                                                • 1.7 用户退出
                                                                                                                              • 第 2 章 用户管理
                                                                                                                                • 2.1 路由及组件
                                                                                                                                • 2.2 面包屑导航及搜索框
                                                                                                                                • 2.3 展示用户列表
                                                                                                                                • 2.4 模糊搜索
                                                                                                                                • 2.5 切换用户状态
                                                                                                                                • 2.6 删除用户
                                                                                                                                • 2.7 添加用户
                                                                                                                                • 2.8 修改用户信息
                                                                                                                                • 2.9 修改用户角色
                                                                                                                              • 第 3 章 权限管理
                                                                                                                                • 3.1 权限列表
                                                                                                                                • 3.2 角色列表
                                                                                                                                • 3.3 删除角色权限
                                                                                                                                • 3.4 修改角色权限
                                                                                                                                • 3.5 权限限制
                                                                                                                                • 3.6 导航守卫
                                                                                                                              • 第 99 章 项目打包及加载优化
                                                                                                                              相关产品与服务
                                                                                                                              内容分发网络 CDN
                                                                                                                              内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
                                                                                                                              领券
                                                                                                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档