Vue 2
1. Vue 基础
1) 环境准备
安装脚手架
1 | npm install -g @vue/cli |
- -g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目
创建项目
1 | vue ui |
安装 devtools
- devtools 插件网址:https://devtools.vuejs.org/guide/installation.html
运行项目
进入项目目录,执行
1 | npm run serve |
修改端口
前端服务器默认占用了 8080 端口,需要修改一下
文档地址:DevServer | webpack
打开 vue.config.js 添加
1
2
3
4
5
6
7
8
9
10const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070
}
})
添加代理
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理
文档地址同上
打开 vue.config.js 添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
Vue 项目结构
1 | PS D:\2022.js\代码\第3章\client> tree src |
- assets - 静态资源
- components - 可重用组件
- router - 路由
- store - 数据共享
- views - 视图组件
以后还会添加
- api - 跟后台交互,发送 fetch、xhr 请求,接收响应
- plugins - 插件
2) Vue 组件
Vue 的组件文件以 .vue 结尾,每个组件由三部分组成
1 | <template></template> |
- template 模板部分,由它生成 html 代码
- script 代码部分,控制模板的数据来源和行为
- style 样式部分,一般不咋关心
入口组件是 App.vue
先删除原有代码,来个 Hello, World 例子
1 | <template> |
解释
- export default 导出组件对象,供 main.js 导入使用
- 这个对象有一个 data 方法,返回一个对象,给 template 提供数据
- 双括号 在 Vue 里称之为插值表达式,用来绑定 data 方法返回的对象属性,绑定的含义是数据发生变化时,页面显示会同步变化
文本插值
1 | <template> |
- 双括号 里只能绑定一个属性,绑定多个属性需要用多个 双括号 分别绑定
- template 内只能有一个根元素
- 插值内可以进行简单的表达式计算
属性绑定
1 | <template> |
- 简写方式:可以省略 v-bind 只保留冒号
事件绑定
1 | <!-- 事件绑定 --> |
- 简写方式:可以把 v-on: 替换为 @
- 在 methods 方法中的 this 代表的是 data 函数返回的数据对象
双向绑定
1 | <template> |
- 用 v-model 实现双向绑定,即
- javascript 数据可以同步到表单标签
- 反过来用户在表单标签输入的新值也会同步到 javascript 这边
- 双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
- 复选框这种标签,双向绑定的 javascript 数据类型一般用数组
计算属性
1 | <!-- 计算属性 --> |
- 普通方法调用必须加 (),没有缓存功能
- 计算属性使用时就把它当属性来用,不加 (),有缓存功能:
- 一次计算后,会将结果缓存,下次再计算时,只要数据没有变化,不会重新计算,直接返回缓存结果
axios
axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 Promise,axios 对 xhr 进行了封装,使之支持 Promise,并提供了对请求、响应的统一拦截功能
安装
1 | npm install axios -S |
导入
1 | import axios from 'axios' |
- axios 默认导出一个对象,这里的 import 导入的就是它默认导出的对象
方法
请求 | 备注 |
---|---|
axios.get(url[, config]) | :star: |
axios.delete(url[, config]) | |
axios.head(url[, config]) | |
axios.options(url[, config]) | |
axios.post(url[, data[, config]]) | :star: |
axios.put(url[, data[, config]]) | |
axios.patch(url[, data[, config]]) |
- config - 选项对象、例如查询参数、请求头…
- data - 请求体数据、最常见的是 json 格式数据
- get、head 请求无法携带请求体,这应当是浏览器的限制所致(xhr、fetch api 均有限制)
- options、delete 请求可以通过 config 中的 data 携带请求体
例子
1 | <template> |
创建实例
1 | const _axios = axios.create(config); |
- axios 对象可以直接使用,但使用的是默认的设置
- 用 axios.create 创建的对象,可以覆盖默认设置,config 见下面说明
常见的 config 项有
名称 | 含义 |
---|---|
baseURL | 将自动加在 url 前面 |
headers | 请求头,类型为简单对象 |
params | 跟在 URL 后的请求参数,类型为简单对象或 URLSearchParams |
data | 请求体,类型有简单对象、FormData、URLSearchParams、File 等 |
withCredentials | 跨域时是否携带 Cookie 等凭证,默认为 false |
responseType | 响应类型,默认为 json |
例
1 | const _axios = axios.create({ |
- 生产环境希望 xhr 请求不走代理,可以用 baseURL 统一修改
- 希望跨域请求携带 cookie,需要配置 withCredentials: true,服务器也要配置 allowCredentials = true,否则浏览器获取跨域返回的 cookie 时会报错
响应格式
名称 | 含义 |
---|---|
data | 响应体数据 :star: |
status | 状态码 :star: |
headers | 响应头 |
- 200 表示响应成功
- 400 请求数据不正确 age=abc
- 401 身份验证没通过
- 403 没有权限
- 404 资源不存在
- 405 不支持请求方式 post
- 500 服务器内部错误
请求拦截器
1 | _axios.interceptors.request.use( |
响应拦截器
1 | _axios.interceptors.response.use( |
条件渲染
1 | <template> |
列表渲染
1 | <template> |
- v-if 和 v-for 不能用于同一个标签
- v-for 需要配合特殊的标签属性 key 一起使用,并且 key 属性要绑定到一个能起到唯一标识作用的数据上,本例绑定到了学生编号上
- options 的 mounted 属性对应一个函数,此函数会在组件挂载后(准备就绪)被调用,可以在它内部发起请求,去获取学生数据
重用组件
按钮组件
1 | <template> |
- 注意,省略了样式部分
使用组件
1 | <template> |
2. Vue 进阶
1) ElementUI
安装
1 | npm install element-ui -S |
引入组件
1 | import Element from 'element-ui' |
测试,在自己的组件中使用 ElementUI 的组件
1 | <el-button>按钮</el-button> |
表格组件
1 | <template> |
分页组件
1 | <template> |
- 三种情况都应该触发查询
- mounted 组件挂载完成后
- 页号变化时
- 页大小变化时
- 查询传参应该根据后台需求,灵活采用不同方式
- 本例中因为是 get 请求,无法采用请求体,只能用 params 方式传参
- 返回响应的格式也许会很复杂,需要掌握【根据返回的响应结构,获取数据】的能力
分页搜索
1 | <template> |
- sex 与 age 均用
''
表示用户没有选择的情况 - age 取值
0,20
会被 spring 转换为new int[]{0, 20}
- age 取值
''
会被 spring 转换为new int[0]
级联选择
级联选择器中选项的数据结构为
1 | [ |
下面的例子是将后端返回的一维数组【树化】
1 | <template> |
2) Vue-Router
vue 属于单页面应用,所谓的路由,就是根据浏览器路径不同,用不同的视图组件替换这个页面内容展示
配置路由
新建一个路由 js 文件,例如 src/router/example14.js,内容如下
1 | import Vue from 'vue' |
- 最重要的就是建立了【路径】与【视图组件】之间的映射关系
- 本例中映射了 3 个路径与对应的视图组件
在 main.js 中采用我们的路由 js
1 | import Vue from 'vue' |
根组件是 Example14View.vue,内容为:
1 | <template> |
- 样式略
- 其中
<router-view>
起到占位作用,改变路径后,这个路径对应的视图组件就会占据<router-view>
的位置,替换掉它之前的内容
动态导入
1 | import Vue from 'vue' |
- 静态导入是将所有组件的 js 代码打包到一起,如果组件非常多,打包后的 js 文件会很大,影响页面加载速度
- 动态导入是将组件的 js 代码放入独立的文件,用到时才加载
嵌套路由
组件内再要切换内容,就需要用到嵌套路由(子路由),下面的例子是在【ContainerView 组件】内定义了 3 个子路由
1 | const routes = [ |
子路由变化,切换的是【ContainerView 组件】中 <router-view></router-view>
部分的内容
1 | <template> |
- redirect 可以用来重定向(跳转)到一个新的地址
- path 的取值为 * 表示匹配不到其它 path 时,就会匹配它
ElementUI 布局
通常主页要做布局,下面的代码是 ElementUI 提供的【上-【左-右】】布局
1 | <template> |
路由跳转
标签式
1 | <el-aside width="200px"> |
编程式
1 | <el-header> |
jump 方法
1 | <script> |
- 其中 this.$router 是拿到路由对象
- push 方法根据 url 进行跳转
导航菜单
1 | <el-menu router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"> |
- 图标和菜单项文字建议用
<span slot='title'></span>
包裹起来 el-menu
标签上加上router
属性,表示结合导航菜单与路由对象,此时,就可以利用菜单项的index
属性来路由跳转
动态路由与菜单
将菜单、路由信息(仅主页的)存入数据库中
1 | insert into menu(id, name, pid, path, component, icon) values |
不同的用户查询的的菜单、路由信息是不一样的
例如:访问 /api/menu/admin
返回所有的数据
1 | [ |
访问 /api/menu/wang
返回
1 | [ |
前端根据他们身份不同,动态添加路由和显示菜单
动态路由
1 | export function addServerRoutes(array) { |
- js 这边只保留几个固定路由,如主页、404 和 login
- 以上方法执行时,将服务器返回的路由信息加入到名为 c 的父路由中去
- 这里要注意组件路径,前面 @/views 是必须在 js 这边完成拼接的,否则 import 函数会失效
重置路由
在用户注销时应当重置路由
1 | export function resetRouter() { |
页面刷新
页面刷新后,会导致动态添加的路由失效,解决方法是将路由数据存入 sessionStorage
1 | <script> |
页面刷新,重新创建路由对象时,从 sessionStorage 里恢复路由数据
1 | const router = new VueRouter({ |
动态菜单
代码部分
1 | <script> |
菜单部分
1 | <el-menu router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" :unique-opened="true"> |
- 没有考虑递归菜单问题,认为菜单只有两级
3) Vuex
入门
vuex 可以在多个组件之间共享数据,并且共享的数据是【响应式】的,即数据的变更能及时渲染到模板
- 与之对比 localStorage 与 sessionStorage 也能共享数据,但缺点是数据并非【响应式】
首先需要定义 state 与 mutations 他们一个用来读取共享数据,一个用来修改共享数据
src/store/index.js
1 | import Vue from 'vue' |
修改共享数据
1 | <template> |
- mutations 方法不能直接调用,只能通过
store.commit(mutation方法名, 参数)
来间接调用
读取共享数据
1 | <template> |
mapState
每次去写 $store.state.name
这样的代码显得非常繁琐,可以用 vuex 帮我们生成计算属性
1 | <template> |
- mapState 返回的是一个对象,对象内包含了 name() 和 age() 的这两个方法作为计算属性
- 此对象配合
...
展开运算符,填充入 computed 即可使用
mapMutations
1 | <template> |
- 类似的,调用 mutation 修改共享数据也可以简化
- mapMutations 返回的对象中包含的方法,就会调用 store.commit() 来执行 mutation 方法
- 注意参数传递略有不同
actions
mutations 方法内不能包括修改不能立刻生效的代码,否则会造成 Vuex 调试工具工作不准确,必须把这些代码写在 actions 方法中
1 | import Vue from 'vue' |
- 首先应当调用 actions 的 updateServerName 获取数据
- 然后再由它间接调用 mutations 的 updateServerName 更新共享数据
页面使用 actions 的方法可以这么写
1 | <template> |
mapActions 会生成调用 actions 中方法的代码
调用 actions 的代码内部等价于,它返回的是 Promise 对象,可以用同步或异步方式接收结果
1
this.$store.dispatch('action名称', 参数)