Vue学习

一、介绍

Vue官方文档:https://cn.vuejs.org/v2/guide/
Vue 是一个采用 MVVM 架构, 通过数据驱动视图的形式来构建用户界面的渐进式框架
“渐进式” 的意思就是从少到多, 从弱到强,需要什么用什么

MVVM

MVVM最早由微软提出来,它借鉴了MVC思想,在前端页面中,把Model(数据)用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离,把Model和View关联起来的就是ViewModel。
ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理
我们并不关心DOM的结构,而是关心数据如何存储。最简单的数据存储方式是使用js对象:

1
2
3
var person = {
name: 'Bart',
}

img
MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而不需要操作繁琐的DOM

虚拟化DOM

在对数据进行操作后并不会将现数据完全覆盖掉原数据,而是放在虚拟DOM中,通过Diff算法与原数据进行比较,得出应施加到真实 DOM 上的改动

客户端路由

Vue-router 是 Vue.js 官方的路由插件,与 Vue.js 深度集成,用于构建单页面应用。Vue 单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来,传统的页面是通过超链接实现页面的切换和跳转的。Vue是单页面应用,使页面局部刷新,不用每次跳转页面都要请求所有数据和dom

组件化开发

前端的业务逻辑也变的非常复杂,再不是传统意义上的一个文件夹放 html、一个文件夹放js、一个文件夹放css就能搞定的。所以我们要分模块。
一个页面可能会有很多模块需要编写,比如导航栏、侧边栏、底部栏,vue将一个网页拆分成单个组件,每个组件都包含属于自己的html、css和js,哪个页面需要就去调用对应的组件

数据代理

vue数据代理: data对象的所有属性的操作(读/写)由vm对象来代理操作

原理:

  1. 通过Object.defineProperty(vm, key, {})给vm添加与data对象的属性对应的属性
  2. 所有添加的属性都包含get/set方法
  3. 在get/set方法中去操作data中对应的属性

获得data中的对象其实是调用了get方法

生命周期

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

beforeCreate() 实例将要创建

created() 实例创建完成之后

beforeMount() 实例将要挂载

mounted() 实例被挂载后调用。Vue完成模板的解析并把真实的DOM元素放入页面后调用mounted

beforeUpdate 将要更新

updated() 更新完成

beforeDestroy() 将要销毁

destroyed() 销毁完成

二、指令

v-bind:

单向绑定,可以用于响应式地更新 HTML attribute。能够接收一个“参数”,在指令名称之后以冒号表示。

在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定。
也可以使用其他的参数名

插值语法用于标签体中的内容,指令语法(以v-开头)管理标签(标签属性、标签体内容、绑定事件) 比如 href=”xxx”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="root">
<h1>Hello,{{name}}</h1>
<!-- 完整语法 -->
<a v-bind:href="url">点击进入我的gitee</a>
<!-- 缩写 -->
<a :href="url">点击进入我的gitee</a>
</div>
<script>
new Vue({
el:'#root',
data:{
name:'点',
url:'https://gitee.com/lingdiand'
}})
</script>

v-model

双向绑定,该指令只能用于表单类元素

v-model:value=”name” 可简写为 v-model=”name”,因为v-model默认收集的就是value值

1
2
3
4
5
6
7
8
9
10
11
 <div id="root">
<!-- 完整语法 -->
<input type="text" v-model:value="name">
<!-- 缩写 -->
<input type="text" v-model="name">
</div>
<script>
const x=new Vue({
el:'#root',
data:{name:'点'}})
</script>

v-on

v-on绑定事件监听,可简写为@

1
2
3
4
<!-- 完整语法 如果传递参数需加小括号-->
<a v-on:click="doSomething()">...</a>
<!-- 缩写 (常用缩写)-->
<a @click="doSomething()">...</a>

v-if

条件渲染

1
2
3
4
5
6
7
8
9
10
11
12
<div id="root">
<p v-if="ok">Yes</p>
<p v-else>No</p>
</div>
<script>
const x=new Vue({
el:'#root',
data:{
"ok":true
}
})
</script>

v-show

当值为false时v-if会移除该元素,而v-show会显示该元素,但是使用 display:none隐藏

1
2
3
<div v-if="show"></div> 	<!--show为数据-->
<div v-else></div>
<div v-show="show"></div>

image-20210716181508191

比如我们有一个编辑员工信息的框,如果员工离职,则需要输入离职日期,在职则不需要输入离职日期(隐藏离职日期框),这时候就要使用v-if,因为v-show仅仅使日期框隐藏,但日期框还是会被渲染,其规则还是存在

v-for

用于展示列表数据

v-for=”(i,index) in list” :key=”i.id” key唯一标识

可循环数组、对象、字符串、指定次数

1
2
3
<ul>
<li v-for="i in list" :key="i.id">{{i.name}}</li>
</ul>

v-text

三、组件介绍

vue.js和vue框架
vue.js只是vue框架里面的其中一个文件,如果只是在单个页面中使用vue,可以看成和JQ一样,简化dom操作
以前的项目是jsp+java,jsp嵌入后端项目中。而现在是前后端分离,页面脱离jsp变为html,但随着前端开发越来越复制,因此就有了遵循模块化的设计。前端项目的代码结构和分层的与后端的框架差不多,再也不是以前那种直接在一个 js 文件里从上到下一顺儿写下来的样子
vue-cli:Vue的脚手架工具,通过它来创建大的项目环境,可以选择需要使用的工具。用于自动生成Vue项目的目录及文件
vue-router: Vue提供的前端路由工具,利用其我们实现页面的路由控制,局部刷新及按需加载,构建单页应用,实现前后端分离。
vuex:Vue提供的状态管理工具,用于统一管理我们项目中各种数据的交互和重用,存储我们需要用到数据对象。
npm:node.js的包管理工具,用于同一管理我们前端项目中需要用到的包、插件、工具、命令等,便于开发和维护,配置文件是package.json。
webpack:一款强大的文件打包工具,可以将我们的前端项目文件同一打包压缩至js中,并且可以通过vue-loader等加载器实现语法转化与加载。

四、npm命令解释

1.说明

  • iinstall 的简写
  • -g 是全局安装,不带 -g 会安装在项目目录,但不会写入package.json,带-g安装在磁盘下
  • -S--save 的简写,安装包信息会写入 dependencies 中。常用这个
    下载第三方插件的时候使用–save,会在package.json配置依赖,方便他人使用我们项目时下载所需依赖
  • -D--save-dev 的简写,安装包写入 devDependencies

2.dependencies 与 devDependencies

  • dependencies 生产阶段的依赖,也就是项目部署服务器运行时的依赖
  • devDependencies (develop)开发阶段的依赖,就是我们在开发过程中需要的依赖,只在开发阶段起作用的

例:开发阶段中编写代码使用的eslint检查代码规范,但在项目上线后,就不需要了,这样就属于devDependencies

如果使用 Element-UI,由于发布到生产后还是依赖 Element-UI,这就可以安装到 dependencies

3.cnpm

npm默认去外网中找依赖,速度很慢,所以需要镜像

1
2
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install

3.总结

npm下载的依赖即可在磁盘也可在项目目录,像vue-cli、hexo、cnpm这些构造项目前使用的工具使用**-g,vue、router、vuex项目中使用的模块使用–save**

使用原则:运行时需要用到的模块使用–save,否则使用–save-dev。

devDependencies 属性下的模块是我们在开发时需要用的,比如项目中使用的 gulp ,压缩css、js的模块。这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装。

使用**–save、–save-dev**会在package.json将模块依赖写入,运行 npm install 初始化项目时,会将模块下载到项目目录下。

五、vue-cli安装

创建Vue项目前需要已经安装了node.js

1.安装vue-cli、 @vue/cli

1
2
3
npm install -g vue-cli	#安装vue-cli,这一步安装的是2.9版本
npm install -g @vue/cli #安装vue-cli,这一步安装的是3.0版本 版本不同有些命令也不同,建议安装3.0
npm install -g @vue/cli-service-global #CLI服务(@vue/cli-service) 是一个开发环境依赖。它是一个 npm包,局部安装在每个@vue/cli创建的项目中。

2.创建项目

注意选择一个工作目录

1
vue create hello-world

提示

1
2
vue create is a Vue CLI 3 only command and you are using Vue CLI 2.9.6.
You may want to run the following to upgrade to Vue CLI 3:

因为我们安装的脚手架是2.9.6版本。2.9.6 创建项目是用 vue init webpack my-project

3.0以上 初始化项目是用 vue create my-project解决方法:

1.使用2.9.6版本的命令vue init webpack my-project
2.卸载2.9.6版本,安装3.0版本

1
2
npm uninstall -g vue-cli
npm install -g @vue/cli

3.选择preset

继续创建项目,会被提示选取一个 preset(可以理解是一个安装模板),默认选择Vue2版本,这里是Vue的版本,和vue-cli插件的版本不是同一个,脚手架只是一个工具,版本的不同只是带来命令格式的不同,而vue版本的不同会导致不同项目的兼容,比如有些第三方插件基于vue2创建,如果使用的是vue3就不能够使用
image-20210717104357687
我们选择第三个 自定义,看一下都会安装哪些东西
光标移动,空格选中
img
这就是脚手架的好处,它把搭建项目所需的东西都集成了,我们可以选择需要的
image-20210717110002197
choose vue version:选择vue版本
Babel :将高级版本ES转换为浏览器识别的JS语法,ES6->ES5
TypeScript: JS的超集,提供了JS面向对象支持
Progressive Web App (PWA) Support PWA Support:使应用向原生APP
Router :路由、请求所对应的地址
Vuex: 数据状态管理器、用于多页面传参
CSS Pre-processors: CSS预处理,将高级CSS语法转换为浏览器识别CSS语法
Linter / Formatter :代码检查工具,语法检测,严格,项目开发推荐
Unit Testing :单元测试
E2E Testing :端端测试

4.一些配置

image-20210717112737018

5.创建完成

image-20210717114122474

六、vue-cli使用

1.结构分析

image-20210717114201105

  1. node_modules:安装的所有工具

  2. package.json:node环境下都有这个文件,用于包管理。我们之前安装的第三方依赖的名字和版本号都在这里面显示,如果项目是别人的,要先进行npm install 安装依赖

    • name - 包名
    • version - 包的版本号
    • description - 包的描述
    • dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下
    • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上
  3. public:indexl.html文件 ,文件入口

  4. src:放置组件和入口文件

    • assets:静态资源
    • components:可复用的组件,单个模块级别的组件,比如导航栏
    • router:路由,用于页面跳转,把准备好路由组件注册到路由里
    • views:存放编写的页面,单个页面级别的组件
    • store:存放公共变量
    • main.js:项目的入口,项目中的所有页面都会加载main.js,是初始化vue实例并使用需要的插件
    • App.vue:是我们的主组件,页面入口文件,所有页面都是在App.vue下进行切换的。也是整个项目的关键,App.vue负责构建定义及页面组件归集。
  5. vue.config.js:vue-cli3 脚手架搭建完成后,项目目录中没有 vue.config.js 文件,需要手动创建。

项目运行后会加载public/index.html,因为脚手架配置了main.js,所以main.js实例化App.vue,App.vue中有两个路由链接。进入route/index.js可以看到两个路由链接的源来自view/Home.vue和About.vue,Home.vue使用了组件components/HelloWorld.vue

2.main.js

main.js编写脚本,App.vue编写页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  引入Vue,这是ES6的引入语法
// 之前需要通过script标签引入
import Vue from 'vue'
// 引入APP组件,它是所有组件的父组件
import App from './App.vue'
import router from './router'
import store from './store'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 创建Vue的实例对象
new Vue({
router,
store,
// 将App组件放入容器中
// render:createElement=>createElement(App)
render: h => h(App)
}).$mount('#app')

3.命令

在README.md文件中有运行项目所需的常用命令

1
2
3
npm install	#安装依赖
npm run serve #在本地运行项目
npm run build #打包项目,用于项目完成后进行打包部署

npm run dev和npm run serve的不同
打开package.json,在scripts中可以看到key:value
当我们运行npm run serve时其实运行的命令是vue-cli-service serve,如果要使用dev,将serve改为dev即可
npm run dev 是 vue-cli2 npm run serve 是 vue-cli3

1
2
3
4
5
6
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},

七、ESLint

如果我们在创建项目时使用了ESLint,当运行时代码不符合ESLint规范时就会报错,但是并不会提示具体报错的位置和信息

VsCode插件中安装ESLint,就可以自动检测不符合规范的代码。但是这样需要我们手动一个一个修复,网上有很多教程但是太复杂了。而我们的需求是:当我们进行格式化文档(shift+alt+F)的时候可以自动帮我们修复格式不正确的代码

打开设置,搜索ESLint,勾选 启用ESLint作为格式化程序

image-20210815131220810

这样当我们进行格式化时会出现选项,选择ESLint即可

image-20210815131548689

image-20210815131611782

八、axios

1.安装

1
npm install axios --save

2.引用

在main.js文件中引入
如果要全局使用axios就需要在main.js中设置成全局的,然后再组件中通过this调用

1
2
3
4
// 因为axios不是vue的插件,所以不能直接用use方法,需要将其加载到原型上。
import axios from 'axios'
// axios需要使用prototype将axios挂载到原型上 ,即自己手动增加了一个Vue的属性。$后面是自己另起的名称,以后就可以使用该名称
Vue.prototype.$axios=axios;

3.使用

第一种

1
2
3
4
5
6
7
8
this.$axios.get("127.0.0.1:8000").then(
(response) => { //如果成功的话返回
console.log("请求成功", response.data);
},
(error) => { //如果失败的话返回
console.log("请求失败", error.message);
}
);

第二种

1
2
3
4
5
6
7
8
9
10
11
this.$axios({
method: 'post', // 指定请求方式
url: '/user/getById', // 请求接口
data: { // 发送的数据
id: 1
}
}).then(res => {
// 接口成功返回结果执行
}).catch((erro) => {
// 接口失败返回结果执行
})

九、vue-router

1.基本路由

  1. 创建路由配置项

    如果没有加路由的组件,第一步应该是创建组件

    在router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.引入vue-router
import VueRouter from 'vue-router'
// 2.创建路由配置,是一个数组对象
const routes = [
{
path: '/', // 浏览器url显示的内容
name: 'Home',
//加载的组件名,即其他页面中定义的
/* export default {
name: 'Home'} */
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
// 3.创建路由器,并把上一步的路由配置加入,因为key和value的名称都为router,可以省略
const router=new VueRouter({
//routes: routes
routes
})
// 4.暴露router
export default router
  1. 引入

    在main.js

    第一步引入的是路由插件,这一步加入我们暴露的router

1
2
3
4
5
6
7
import router from './router'
//在nue Vue中加入router
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
  1. router-link标签

    创建:第一步配置了路由,路径和组件之间的映射;

    注册:第二步在App中加入了路由,即vue知道了我们的路由配置;

    使用:第三步可以使用我们的路由配置了,配置路由链接(相当于a链接)和需要展示组件的区域

    在导航栏中(App.vue)

1
2
3
4
5
//实现切换 to表示路径,指第一步配置的组件路径需要加/,
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
//展示组件位置 <router-view/>
<router-view></router-view>

2.嵌套(多级)路由

Home组件原本只有文本内容,显示需要显示两个导航链接news、message

  1. 新建两个组件news和message并配置好暴露的名称

  2. 编写路由规则children属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    path: '/about',
    name: 'About',
    component: Home,
    children: [
    {
    // 一级路由需要加/,其他不需要,会自动加上
    path: 'news',
    component: News
    }, {
    path: 'message',
    component: Message
    }]}
  3. 在父组件Home中使用 二级路由使用:父路径+子路径

    1
    2
    <router-link to="/home/news">News</router-link> |
    <router-link to="/home/message">Message</router-link>

十、vue-element-admin框架

1.登录接口请求详解

  • 页面:用户单击登录,通过store转发到user的login方法

  • store/user:共享数据。login方法调用api/user下的login方法

  • api/user:请求处理。login方法下的url为/uesr/login,mock进行拦截

  • mock:模拟数据。mock中有/user/login,匹配到api的/user/login,创建token名为XXX-token和code并返回。code值可以在request.js中查看

  • store中返回token,因为request封装了axios,会根据返回的code判断是否成功,成功调用utils/auth将token存储cookie中,失败则给出错误提示

    我们通过用户名获得对应的token,该token是唯一标示用户身份的一个key,再通过token获得对应的用户信息,其中只有token存储在本地

    permission会先从 cookie 中查看是否存有 token,没有就执行登录流程,如果有token,就会把这个 token 返给后端user/info接口去拉取user_info。

login返回数据格式

1
2
3
4
5
6
{
"code": 20000,
"data": {
"token": "admin-token"
}
}

info返回数据格式

1
2
3
4
5
6
7
8
9
{
"code":20000,
"data":{
"roles":["admin"],
"introduction":"I am a super administrator",
"avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
"name":"Super Admin"
}
}

logout返回数据格式

1
2
3
4
{
"code":20000,
"data":"success"
}

2.更改为自己后台接口

更改前端接口

  • .env.development:VUE_APP_BASE_AP更改为自己后台的接口
  • vue.config.js:注释掉before: require(‘./mock/mock-server.js’),before属性是引入mock.js用前端产生假数据来进行前后交互
  • @api中url更改为后台提供的api

后台提供api并返回相应的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
@RequestMapping("user")
public class LoginController {
@PostMapping (value = "login")
public Map login(String username, String password) {
HashMap<String, Object> response = new HashMap<>();
HashMap<String, Object> responseData = new HashMap<>();
responseData.put("token",1);
response.put("code",20000);
response.put("message","登录成功");
response.put("data",responseData);
return response;
}
@GetMapping(value = "info")
public Map info() {
HashMap<String, Object> responseInfo = new HashMap<>();
HashMap<String, Object> responseData = new HashMap<>();
responseData.put("roles","admin");
responseData.put("name","Super admin123456");
responseData.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
responseInfo.put("code",20000);
responseInfo.put("message","登录成功");
responseInfo.put("data",responseData);
return responseInfo;
}
}
@PostMapping(value = "logout")
public Map logout() {
HashMap<String, Object> responseData = new HashMap<>();
responseData.put("code", 20000);
responseData.put("message", "退出成功");
responseData.put("data", "success");
return responseData;
}

这个是假登录,不会判断输入信息的正确性,如果需要自定义账户,采用下面这种

创建一个user.properties,自定义用户信息

1
2
3
4
5
6
user.token=admin-token
user.roles[0]=admin
user.introduction=I am a super administrator
user.avatar=https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif
user.username=admin
user.password=123456

创建一个User类,加载指定的配置文件,将用户信息存入其中

1
2
3
4
5
6
7
8
9
10
11
@PropertySource("classpath:user.properties")
@Component
@ConfigurationProperties(prefix = "user")
public class User {
private String token;
private List<String> roles;
private String introduction;
private String avatar;
private String name;
private String username;
private String password;

登录,将入参用户名和密码与配置文件数据进行对比,成功返回一个token,值为 用户名-token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Resource
User userInfo; //在方法外声明,该对象中保存了配置文件中的数据

@PostMapping(value = "login")
public Map login(@RequestBody User loginUser) {
String username = loginUser.getUsername();
String password = loginUser.getPassword();
HashMap<String, Object> response = new HashMap<>();
HashMap<String, Object> responseData = new HashMap<>();
//如果用户名和密码正确
if (userInfo.getUsername().equals(username) && userInfo.getPassword().equals(password)) {
//根据用户名生成token
responseData.put("token", username + "-token");
response.put("code", 20000);
response.put("message", "登录成功");
response.put("data", responseData);
} else {
response.put("code", 60204);
response.put("message", "账号或密码错误");
}
return response;
}

获得用户信息,根据入参token判断是否与配置文件数据相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping(value = "info")
public Map info(@RequestParam("token") String token) {
HashMap<String, Object> responseInfo = new HashMap<>();
//如果token匹配
if (userInfo.getToken().equals(token)) {
responseInfo.put("code", 20000);
responseInfo.put("message", "登录成功");
responseInfo.put("data", userInfo);
} else {
responseInfo.put("code", 50008);
responseInfo.put("message", "登录失败,不能获得用户信息");
}
return responseInfo;
}

3.跨域问题

产生 跨域的主要问题是 协议、域名、端口号 三者有一点不同就会产生跨域问题

  • 全局配置

在后台项目中添加配置文件CORSConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class CORSConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //限定访问路径为
.allowedOrigins("*") //允许来自*跨域访问,还有一种是这样allowedOriginPatterns("*")
.allowedMethods("GET","POST","PUT","OPTIONS","DELETE","PATCH") //限定访问方法
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
  • 局部配置

在需要使用跨域的接口、方法中使用注解@CrossOrigin(origins = “http://localhost:9527”)。参数可省


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!