目录
1. 环境准备
1.1安装脚手架
npm install -g @vue/cli
-
-g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目
1.2 创建项目
进入到你要创建服务器的目录下,打开命令窗口,执行命令
vue ui
这个的意思就是使用图形界面向导来创建vue项目,运行后跳出此界面
选择手动配置项目
添加 vue router 和 vuex
选择版本,创建项目
这样就会在文件夹中自动生成vue项目啦~
1.3 安装 vue devtools
这是一个可以进行调试vue项目的浏览器插件,很好用
如果 谷歌应用商店进不去,可以试试以下方法。
1. 先到一个插件下载网址下载插件
下载成功后并解压后就会有下图的.crx后缀文件 。
2. 打开谷歌浏览器, 点击右上角三个点,更多工具——扩展程序
进入到如下页面,并打开右上角的开发者模式。
然后将下载好的.crx后缀文件拖进此浏览器页面中即可。
1.4 运行项目
进入项目目录,prowershell窗口,执行命令
npm run serve
如果报错,尝试用管理员身份运行prowershell,然后进入到项目目录,在执行上述命令。
修改端口
如果前端服务器默认占用了后端 8080 端口,可以进行修改
打开 vue.config.js 文件添加
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070
}
})
添加代理
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理
打开 vue.config.js 文件添加
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
vue的项目结构
-
assets - 静态资源
-
components - 可重用组件
-
router - 路由
-
store - 数据共享
-
views - 视图组件
以后还会添加
-
api - 跟后台交互,发送 fetch、xhr 请求,接收响应
-
plugins - 插件
2 Vue组件
Vue 的组件文件以 .vue 结尾,每个组件由三部分组成
<template></template>
<script></script>
<style></style>
-
template 模板部分,由它生成 html 代码
-
script 代码部分,控制模板的数据来源和行为
-
style 样式部分,一般不咋关心
入口组件是 App.vue
先删除原有代码,来个 Hello, World 例子
<template>
<h1>{{msg}}</h1>
</template>
<script>
export default {
data() {
return {
msg: "Hello, Vue!"
}
}
}
</script>
解释
-
export default 导出组件对象,供 main.js 导入使用
-
这个对象有一个 data 方法,返回一个对象,给 template 提供数据
-
{{}}
在 Vue 里称之为插值表达式,用来绑定 data 方法返回的对象属性,绑定的含义是数据发生变化时,页面显示会同步变化
2.1 文本插值
<template>
<div>
<h1>{{ name }}</h1>
<h1>{{ age > 60 ? '老年' : '青年' }}</h1>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '张三', age: 70 };
}
};
export default options;
</script>
-
{{}}
里只能绑定一个属性,绑定多个属性需要用多个{{}}
分别绑定 -
template 内只能有一个根元素
-
插值内可以进行简单的表达式计算
2.2 属性绑定 v-bind
vue中可以将组件和时间进行绑定。如下代码
<template>
<div>
<!-- 属性绑定 v-bind -->
<div><input type="text" v-bind:value="name"/></div>
<div><input type="date" v-bind:value="birthday"/></div>
<!-- v-bind简写 v-bind可以省略 -->
<div><input type="text" :value="age"/></div>
</div>
</template>
<script>
const options = {
data:function(){
return {name: '张三',birthday: '1995-01-01',age: 18}
}
}
export default options;
</script>
-
简写方式:可以省略 v-bind 只保留冒号
2.3 事件绑定 v-on
<!-- 事件绑定 -->
<template>
<div>
<div><input type="button" value="点我执行m1" v-on:click="m1"></div>
<div><input type="button" value="点我执行m2" @click="m2"></div>
<div>{{count}}</div>
</div>
</template>
<script>
const options = {
data: function () {
return { count: 0 };
},
methods: {
m1() {
this.count ++;
console.log("m1")
},
m2() {
this.count --;
console.log("m2")
}
}
};
export default options;
</script>
-
简写方式:可以把 v-on: 替换为 @
-
在 methods 方法中的 this 代表的是 data 函数返回的数据对象
2.4 计算属性 computed
<!-- 计算属性 -->
<template>
<div>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
</template>
<script>
const options = {
data: function () {
return { firstName: '三', lastName: '张' };
},
/* methods: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
},*/
computed: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
}
};
export default options;
-
普通方法调用必须加 (),没有缓存功能
-
计算属性使用时就把它当属性来用,不加 (),有缓存功能:
-
一次计算后,会将结果缓存,下次再计算时,只要数据没有变化,不会重新计算,直接返回缓存结果
-
2.5 vue 底层展示界面的原理
这里我们那App.vue举例。因为它是vue项目创建出来后自带的
首先,先将App.vue导入到main.js文件中
然后在main.js中执行了new Vue中的render
那么,这个#app的文件又是啥呢,其实在vue项目中已经给我们默认创建出来了
如图:
这就是vue将组件渲染到页面上的原理啦~
3. Axios
axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 Promise,axios 对 xhr 进行了封装,使之支持 Promise,并提供了对请求、响应的统一拦截功能
安装
npm install axios -S
导入
import axios from 'axios'
-
axios 默认导出一个对象,这里的 import 导入的就是它默认导出的对象
方法
请求 | 备注 |
---|---|
axios.get(url[, config]) | ⭐️ |
axios.delete(url[, config]) | |
axios.head(url[, config]) | |
axios.options(url[, config]) | |
axios.post(url[, data[, config]]) | ⭐️ |
axios.put(url[, data[, config]]) | |
axios.patch(url[, data[, config]]) |
-
config - 选项对象、例如查询参数、请求头...
-
data - 请求体数据、最常见的是 json 格式数据
-
get、head 请求无法携带请求体,这应当是浏览器的限制所致(xhr、fetch api 均有限制)
-
options、delete 请求可以通过 config 中的 data 携带请求体
代码演示(这里包含了 发送get、post请求,发送请求头,发送是携带查询参数,用格式urlencode 请求体发送数据,用multipart格式请求体发送数据,用json格式请求体发送数据)
代码:提示,这里我配置了跨域的问题,/stu 代表localhost:8080,
前端代码:
<template>
<!-- axios -->
<div>
{{data}}
<input type="button" value="点击获取数据" @click="sendAsync">
</div>
</template>
<script>
/* 导入axios */
import axios from '../util/myaxios'
const options = {
data:function(){
return { data:''}
},
methods: {
async sendAsync(){
// 发送 get请求
// const resp = await axios.get('stu/a1');
//发送 post请求,需要携带一个参数对象,这里为空对象
// const resp = await axios.post('stu/a2',{});
//发送请求头,除了请求地址,参数,还有config对象
// const resp = await axios.post('stu/a3',{},{
// headers:{
// Authorization: 'authorization'
// }
// });
//发送请求时携带查询参数, url?name=xxx&age=xxx
/* 第一种方法,拼接字符串 */
// const name = encodeURIComponent('&&&');
// const age = 18;
// const resp = await axios.post(`stu/a4?name=${name}&age=${age}`);
/* 只是这种方法对于特殊字符串,需要重新编码 ,例如&&&*/
/*
第二种方法,就是在config对象中设置param对象
这种方法会自动对特殊字符进行重新编码
*/
// const resp = await axios.post('stu/a4',{},{
// params:{
// name: '&&&',
// age: 18
// }
// });
//用请求体发送数据,格式为urlencoded
// const param = new URLSearchParams();
// param.append('name','张三');
// param.append('age','18');
// const resp = await axios.post('stu/a4',param);
//用请求体发送数据,格式为 multipart
// const param = new FormData();
// param.append('name','李四');
// param.append('age','19');
// const resp = await axios.post('stu/a4',param);
// //用请求体发送数据,格式为json,需在后端加上@RequestBody注解
// const resp = await axios.post('stu/a5',{
// name: '张三',
// age: 18
// });
this.data = resp.data
console.log(resp)
},
sendSync(){
axios.get('stu/student')
.then(resp =>{
console.log(resp)
this.data = resp.data
})
}
}
}
export default options;
</script>
后端:
为了方便,代码都写在可Controller层
package com.hua.controller;
import com.hua.domain.A5;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
/**
* @Author 华子
* @Date 2023/1/8 15:51
* @Version 1.0
*/
@Controller
@RequestMapping("stu")
public class AxiosController {
@GetMapping("/a1")
@ResponseBody
public String a1(){
return "get request";
}
@PostMapping("/a2")
@ResponseBody
public String a2(){
return "post request";
}
@PostMapping("/a3")
@ResponseBody
public String a3(@RequestHeader("Authorization") String authorization){
System.out.println("Authorization:"+authorization);
return "post request";
}
@PostMapping("/a4")
@ResponseBody
public String a4(String name, Integer age){
System.out.println("name:"+name+",age:"+age);
return "post request";
}
@PostMapping("/a5")
@ResponseBody
public String a5(@RequestBody A5 a5){
System.out.println("name:"+a5.getName()+",age:"+a5.getAge());
return "post request";
}
@PostMapping("/a6Set")
@ResponseBody
public String a6Set(HttpSession session){
System.out.println("==========a6Set==============");
System.out.println(session.getId());
session.setAttribute("name","张三");
return "post request";
}
@PostMapping("/a6Get")
@ResponseBody
public String a6Get(HttpSession session){
System.out.println("==========a6Get==============");
System.out.println(session.getId());
System.out.println(session.getAttribute("name"));
return "post request";
}
}
axios可以进行默认配置
创建实例
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 |
例
const _axios = axios.create({
baseURL: 'http://localhost:8080',
withCredentials: true
});
await _axios.post('/api/a6set')
await _axios.post('/api/a6get')
-
生产环境希望 xhr 请求不走代理,可以用 baseURL 统一修改
-
希望跨域请求携带 cookie,需要配置 withCredentials: true,服务器也要配置 allowCredentials = true,否则浏览器获取跨域返回的 cookie 时会报错
这里提供一个默认写好的axios配置~
myaxios.js文件 (里头有拦截器,包括请求和响应两种拦截器)
import axios from 'axios'
const _axios = axios.create({
// baseURL: 'http://localhost:8080',
withCredentials: true
});
// 9. 拦截器
_axios.interceptors.request.use(
function (config) {
// 比如在这里添加统一的 headers
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function (response) {
// 2xx 范围内走这里
return response;
},
function (error) {
if (error.response?.status === 400) {
console.log('请求参数不正确');
return Promise.resolve(400);
} else if (error.response?.status === 401) {
console.log('跳转至登录页面');
return Promise.resolve(401);
} else if (error.response?.status === 404) {
console.log('资源未找到');
return Promise.resolve(404);
}
// 超出 2xx, 比如 4xx, 5xx 走这里
return Promise.reject(error);
}
);
export default _axios;
我们导入axios就可以这么写
4. 列表渲染v-for和条件渲染 v-if
这里用一个例子来展示
从后端拿去学生数据,展示到前端
<template>
<div>
<!-- <input type="button" value="点击获取数据" @click="sendAsync"> -->
<div class="title">学生列表</div>
<div class="thead">
<div class="row bold">
<div class="col">编号</div>
<div class="col">姓名</div>
<div class="col">性别</div>
<div class="col">年龄</div>
</div>
</div>
<div class="tbody">
<!-- 条件渲染v-if:如果读取到学生数据不为空,就展示 -->
<div v-if="students.length > 0">
<!-- 列表渲染v-for,循环得到的学生数据并展示 -->
<div class="row" v-for="student of students" :keys="student.id">
<div class="col">{{student.id}}</div>
<div class="col">{{student.name}}</div>
<div class="col">{{student.sex}}</div>
<div class="col">{{student.age}}</div>
</div>
</div>
<!-- 如果为空 -->
<div class="row" v-else>暂无学生数据</div>
</div>
</div>
</template>
<script>
import axios from '../util/myaxios'
const options = {
mounted: function(){
this.sendAsync();
},
data: function(){
return {
students: []
}
},
methods:{
async sendAsync(){
const resp = await axios.get('stu/student');
console.log(resp.data.data)
this.students = resp.data.data
}
}
}
export default options;
</script>
<style scoped>
div {
font-family: 华文行楷;
font-size: 20px;
}
.title {
margin-bottom: 10px;
font-size: 30px;
color: #333;
text-align: center;
}
.row {
background-color: #fff;
display: flex;
justify-content: center;
}
.col {
border: 1px solid #f0f0f0;
width: 15%;
height: 35px;
text-align: center;
line-height: 35px;
}
.bold .col {
background-color: #f1f1f1;
}
</style>
重用组件:
主要语法就是在components文件夹中定义一个常用组件,也就是子组件
然后导入到父组件中
<script>
import MyButton from '../components/MyButton.vue'
const options = {
components:{
MyButton
}
}
export default options;
</script>
在父组件中打上标签即可。
<template>
<!-- 可重用组件 -->
<div>
<h1>父组件</h1>
<div>
<h2>子组件</h2>
<my-button>1</my-button>
</div>
</div>
</template>
具体代码:
子组件MyButtons:
<template>
<div class="button" :class="[type,size]">
<slot></slot><!-- 加入插槽,可以在父组件中添加按钮名称 -->
</div>
</template>
<script>
const options = {
props:["type","size"]
};
export default options;
</script>
<style scoped>
.button {
display: inline-block;
text-align: center;
border-radius: 30px;
margin: 5px;
font: bold 12px/25px Arial, sans-serif;
padding: 0 2px;
text-shadow: 1px 1px 1px rgba(255, 255, 255, .22);
box-shadow: 1px 1px 1px rgba(0, 0, 0, .29), inset 1px 1px 1px rgba(255, 255, 255, .44);
transition: all 0.15s ease;
}
.button:hover {
box-shadow: 1px 1px 1px rgba(0, 0, 0, .29), inset 1px 1px 2px rgba(0, 0, 0, .5);
}
.button:active {
box-shadow: inset 1px 1px 2px rgba(0, 0, 0, .8);
}
.primary {
background-color: #1d6ef9;
color: #b5e3f1;
}
.danger {
background-color: rgb(196, 50, 50);
color: white;
}
.success {
background-color: #a5cd4e;
;
color: #3e5706;
}
.small {
width: 40px;
height: 20px;
font-size: 10px;
line-height: 20px;
}
.middle {
width: 50px;
height: 25px;
font-size: 14px;
line-height: 25px;
}
.large {
width: 60px;
height: 30px;
font-size: 18px;
line-height: 30px;
}
</style>
父组件:
<template>
<!-- 可重用组件 -->
<div>
<h1>父组件</h1>
<div>
<h2>子组件</h2>
<my-button type="primary" size="small">1</my-button>
<my-button type="danger" size="middle">2</my-button>
<my-button type="success" size="large">3</my-button>
</div>
</div>
</template>
<script>
import MyButton from '../components/MyButton.vue'
const options = {
components:{
MyButton
}
}
export default options;
</script>
<style scoped>
.button {
display: inline-block;
text-align: center;
border-radius: 30px;
margin: 5px;
font: bold 12px/25px Arial, sans-serif;
padding: 0 2px;
text-shadow: 1px 1px 1px rgba(255, 255, 255, .22);
box-shadow: 1px 1px 1px rgba(0, 0, 0, .29), inset 1px 1px 1px rgba(255, 255, 255, .44);
transition: all 0.15s ease;
}
.button:hover {
box-shadow: 1px 1px 1px rgba(0, 0, 0, .29), inset 1px 1px 2px rgba(0, 0, 0, .5);
}
.button:active {
box-shadow: inset 1px 1px 2px rgba(0, 0, 0, .8);
}
.primary {
background-color: #1d6ef9;
color: #b5e3f1;
}
.danger {
background-color: rgb(196, 50, 50);
color: white;
}
.success {
background-color: #a5cd4e;
;
color: #3e5706;
}
.small {
width: 40px;
height: 20px;
font-size: 10px;
line-height: 20px;
}
.middle {
width: 50px;
height: 25px;
font-size: 14px;
line-height: 25px;
}
.large {
width: 60px;
height: 30px;
font-size: 18px;
line-height: 30px;
}
</style>