Vue3+Element-Plus
前置知识补充
插件安装
HTML CSS Support
在编写样式表的时候,自动补全功能大大缩减了编写时间。
JavaScript (ES6) code snippets
支持ES6语法提示
Mithril Emmet
一个能大幅度提高前端开发效率的一个工具,用于补全代码
Path Intellisense
路径提示插件
Vue 3 Snippets
在 Vue 2 或者 Vue 3 开发中提供代码片段,语法高亮和格式化的 VS Code 插件,能极大提高你的开发效率。
Auto Close Tag
自动闭合HTML/XML标签
Auto Rename Tag
自动完成另一侧标签的同步修改
open in browser
vscode不像IDE一样能够直接在浏览器中打开html,而该插件支持快捷键与鼠标右键快速在浏览器中打开html文件,支持自定义打开指定的浏览器,包括:Firefox,Chrome,Opera,IE以及Safari
Live Server
一个具有实时加载功能的小型服务器,也就是说我们可以在项目中实时用live-server作为一个实时服务器实时查看开发的网页或项目效果。
Vue Language Features (Volar)
一个专门为 Vue 3 构建的语言支持插件。它基于@vue/reactivity按需计算一切,实现原生 TypeScript 语言服务级别的性能。
TypeScript Vue Plugin (Volar)
TypeScript Vue Plugin是一个对Vue.js框架进行扩展的插件,它允许开发者使用TypeScript语言编写Vue组件和应用程序,并提供了更好的类型检查和编辑器支持。通过使用TypeScript Vue Plugin,开发者可以在编写Vue应用时获得更好的开发体验和代码可维护性。
File Utils
File Utils插件,可以方便快捷的来创建、复制、移动、重命名文件和目录。
IntelliJ IDEA Keybindings
安装VSCode的插件 IntelliJ IDEA Keybindings 即可在VSCode中使用IDEA的快捷键。
JavaScript-导入导出
之前的导入js文件是把整个外部文件全部导入,那么文件很大很多时会比较臃肿。这里可以选择部分导入。注意:这里要用到Live Server插件。因为是由于跨源资源共享(CORS)策略导致的。在现代浏览器中,出于安全考虑,默认情况下不允许从一个源(origin)加载来自另一个源的资源,除非目标源明确允许这种跨源请求。
所有运行时不能直接从浏览器打开,要运行本土服务器
JS提供的导入导出机制,可以实现按需导入。
showMessage.js
//简单的展示信息
//添加关键字exprot,实现导出
export function simpleMessage(msg){
console.log(msg)
}
//复杂的展示信息
export function complexMessage(msg){
console.log(new Date()+": "+msg)
}
message.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js导入导出</title>
</head>
<body>
<div id="app">
<button id="btn">点我展示信息</button>
</div>
<script type="module">
// import {模块名称} from '文件地址名称'
import {complexMessage} from './showMessage.js';
document.getElementById("btn").onclick=function(){
complexMessage("点击了");
}
</script>
</body>
</html>
批量导出
如果每个函数前都添加export会显得很麻烦。这里支持统一导出
//简单的展示信息
//添加关键字exprot,实现导出
function simpleMessage(msg){
console.log(msg)
}
//复杂的展示信息
function complexMessage(msg){
console.log(new Date()+": "+msg)
}
//统一导出
export {simpleMessage, complexMessage}
别名 as
可以在导入或者导出时起别名,需要注意的是有了别名,原名就不能用了
js文件
export {simpleMessage as sm, complexMessage}
html文件
<script type="module">
// import {模块名称} from '文件地址名称'
import {complexMessage as cm} from './showMessage.js';
document.getElementById("btn").onclick=function(){
cm("点击了");
}
</script>
默认导出
当模块很多时一个一个写会很麻烦。可以用默认导出
js文件
export default {simpleMessage, complexMessage}
html文件
<script type="module">
// import {模块名称} from '文件地址名称'
import ario from './showMessage.js';
document.getElementById("btn").onclick=function(){
ario.complexMessage("点击了");
}
</script>
这里可以任意起名来导入默认导出。后面又点运算符使用。
Vue3
前面已经学过Vue2了,可以查看Vue2 - 韩熙隐ario - 博客园
这里简单回顾一下
Vue是什么
Vue 是一款用于构建用户界面的渐进式的JavaScript框架。 (官方:https://cn.vuejs.org/)
将数据渲染到前端界面
之所以说是渐进是框架,是因为一个项目可以局部使用Vue,也可以整站使用Vue
局部使用Vue
快速入门
- 准备
- 准备html页面,并引入Vue模块(官方提供)
- 创建Vue程序的应用实例
- 准备元素(div),被Vue控制
- 构建用户界面
- 准备数据
- 通过插值表达式渲染页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 插值表达式 -->
<h1>{{msg}}</h1>
</div>
<!-- 要载入才能渲染 -->
<div >
<h1>{{msg}}</h1>
</div>
<!-- 引入vue模块 -->
<script type="module">
//用新的导入方式
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
/* 创建vue的应用实例 */
createApp({
data(){
return {
//定义数据
msg: 'hello vue3'
}
}
}).mount("#app");
</script>
</body>
</html>
常用指令
这里已经在Vue2 - 韩熙隐ario - 博客园已经学过了。这里可以回顾补充一下
指令:HTML标签上带有 v-前缀的特殊属性,不同的指令具有不同的含义,可以实现不同的功能。
常用指令:
指令 | 作用 |
---|---|
v-for | 列表渲染,遍历容器的元素或者对象的属性 |
v-bind | 为HTML标签绑定属性值,如设置 href , css样式等 |
v-if/v-else-if/v-else | 条件性的渲染某元素,判定为true时渲染,否则不渲染 |
v-show | 根据条件展示某元素,区别在于切换的是display属性的值 |
v-model | 在表单元素上创建双向数据绑定 |
v-on | 为HTML标签绑定事件 |
v-for
- 作用:列表渲染,遍历容器的元素或者对象的属性
- 语法: v-for = "(item,index) in items"
- 参数说明:
- items 为遍历的数组
- item 为遍历出来的元素
- index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = "item in items"
- 参数说明:
注意:遍历的数组,必须在data中定义; 要想让哪个标签循环展示多次,就在哪个标签上使用 v-for 指令。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<table border="1 solid" colspa="0" cellspacing="0">
<tr>
<th>文章标题</th>
<th>分类</th>
<th>发表时间</th>
<th>状态</th>
<th>操作</th>
</tr>
<!-- 哪个元素要出现多次,v-for指令就添加到哪个元素上 -->
<tr v-for="(article,index) in articleList">
<td>{{article.title}}</td>
<td>{{article.category}}</td>
<td>{{article.time}}</td>
<td>{{article.state}}</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
<!-- <tr>
<td>标题2</td>
<td>分类2</td>
<td>2000-01-01</td>
<td>已发布</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
<tr>
<td>标题3</td>
<td>分类3</td>
<td>2000-01-01</td>
<td>已发布</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr> -->
</table>
</div>
<script type="module">
//导入vue模块
import { createApp} from
'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
//创建应用实例
createApp({
data() {
return {
//定义数据
articleList:[{
title:"医疗反腐绝非砍医护收入",
category:"时事",
time:"2023-09-5",
state:"已发布"
},
{
title:"中国男篮缘何一败涂地?",
category:"篮球",
time:"2023-09-5",
state:"草稿"
},
{
title:"华山景区已受大风影响阵风达7-8级,未来24小时将持续",
category:"旅游",
time:"2023-09-5",
state:"已发布"
}]
}
}
}).mount("#app")//控制页面元素
</script>
</body>
</html>
v-bind
- 作用:动态为HTML标签绑定属性值,如设置href,src,style样式等。
- 语法:v-bind:属性名="属性值"
- 简化::属性名="属性值"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- <a v-bind:href="url">黑马官网</a> -->
<a :href="url">黑马官网</a>
</div>
<script type="module">
//引入vue模块
import { createApp} from
'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
//创建vue应用实例
createApp({
data() {
return {
url: 'https://www.itheima.com'
}
}
}).mount("#app")//控制html元素
</script>
</body>
</html>
v-if & v-show
- 作用:这两类指令,都是用来控制元素的显示与隐藏的
- v-if
- 语法:v-if="表达式",表达式值为 true,显示;false,隐藏
- 其它:可以配合 v-else-if / v-else 进行链式调用条件判断
- 原理:基于条件判断,来控制创建或移除元素节点(条件渲染)
- 场景:要么显示,要么不显示,不频繁切换的场景
- v-show
- 语法:v-show="表达式",表达式值为 true,显示;false,隐藏
- 原理:基于CSS样式display来控制显示与隐藏
- 场景:频繁切换显示隐藏的场景。比如导航栏悬浮子菜单的显示与隐藏。如果荣v-if,要不断地创建和移除节点性能不高
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
手链价格为: <span v-if="customer.level>=0 && customer.level<=1">9.9</span>
<span v-else-if="customer.level>=2 && customer.level<=4">19.9</span>
<span v-else>29.9</span>
<br/>
手链价格为: <span v-show="customer.level>=0 && customer.level<=1">9.9</span>
<span v-show="customer.level>=2 && customer.level<=4">19.9</span>
<span v-show="customer.level>=5">29.9</span>
</div>
<script type="module">
//导入vue模块
import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
//创建vue应用实例
createApp({
data() {
return {
customer:{
name:'张三',
level:2
}
}
}
}).mount("#app")//控制html元素
</script>
</body>
</html>
v-on
- 作用:为html标签绑定事件
- 语法:
- v-on:事件名="函数名"
- 简写为 @事件名="函数名"
createApp({ data(){需要用到的数据}, methods:{需要用到的方法} })
注意:函数需要定义在methods选项内部
<body>
<div id="app">
<button v-on:click="money">点我有惊喜</button>
<button @click="love">再点更惊喜</button>
</div>
<script type="module">
//导入vue模块
import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
//创建vue应用实例
createApp({
data() {
return {
//定义数据
}
},
methods:{
money: function(){
alert('送你钱100')
},
love: function(){
alert('爱你一万年')
}
}
}).mount("#app");//控制html元素
</script>
</body>
v-model
- 作用:在表单元素上使用,双向数据绑定。可以方便的 获取 或 设置 表单项数据
- 语法:v-model="变量名"
注意:v-model 中绑定的变量,必须在data中定义。
示例可以查看Vue2 - 韩熙隐ario - 博客园双向绑定的部分。
注意:this就指当前的示例对象。可以在函数中当前属性
声明周期
生命周期:指一个对象从创建到销毁的整个过程。
生命周期的八个阶段:每个阶段会自动执行一个生命周期方法(钩子), 让开发者有机会在特定的阶段执行自己的代码
下面的红色框就是钩子
状态 | 阶段周期 |
---|---|
beforeCreate | 创建前 |
created | 创建后 |
beforeMount | 载入前 |
mounted | 挂载完成 |
beforeUpdate | 数据更新前 |
updated | 数据更新后 |
beforeUnmount | 组件销毁前 |
unmounted | 组件销毁后 |
注意:这里的钩子和data,methods时一个级别的。这里一般至少掌握 mounted()
<script type="module">
import { createApp } from 'https://.../vue.esm-browser.js'
const app = createApp({
data() {
return {
message: "Hello Vue"
}
}, //生命周期-钩子函数 mounted
mounted() {
console.log('Vue挂载完毕, 发送请求获取数据 ...');
}
}).mount("#app");
</script>
Vue生命周期典型的应用场景 ?
- 在页面加载完毕时,发起异步请求,加载数据,渲染页面。
Axios
- 介绍:Axios 对原生的Ajax进行了封装,简化书写,快速开发。
- 官网:https://www.axios-http.cn/
Axios使用步骤
- 引入Axios的js文件(参照官网)
- 使用Axios发送请求,并获取相应结果
Axios-请求方式别名【推荐使用】
- 为了方便起见,Axios已经为所有支持的请求方法提供了别名
- 格式:axios.请求方式(url [, data [, config]])
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入axios的js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
/* 发送请求 */
/* axios({
method:'get',
url:'http://localhost:8080/article/getAll'
}).then(result=>{
//成功的回调
//result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
console.log(result.data);
}).catch(err=>{
//失败的回调
console.log(err);
}); */
let article = {
title: '明天会更好',
category: '生活',
time: '2000-01-01',
state: '草稿'
}
/* axios({
method:'post',
url:'http://localhost:8080/article/add',
data:article
}).then(result=>{
//成功的回调
//result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
console.log(result.data);
}).catch(err=>{
//失败的回调
console.log(err);
}); */
//别名的方式发送请求
/* axios.get('http://localhost:8080/article/getAll').then(result => {
//成功的回调
//result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
console.log(result.data);
}).catch(err => {
//失败的回调
console.log(err);
}); */
axios.post('http://localhost:8080/article/add', article).then(result => {
//成功的回调
//result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
console.log(result.data);
}).catch(err => {
//失败的回调
console.log(err);
});
</script>
</body>
</html>
注意:这里的请求路径是运行在本地的Java后端程序。这样前后端就完美结合了。
案例
使用表格展示所有文章的数据, 并完成条件搜索功能
- 钩子函数mounted中, 获取所有的文章数据
- 使用v-for指令,把数据渲染到表格上展示
- 使用v-model指令完成表单数据的双向绑定
- 使用v-on指令为搜索按钮绑定单击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
文章分类: <input type="text" v-model="searchConditions.category">
发布状态: <input type="text" v-model="searchConditions.state">
<button v-on:click="search">搜索</button>
<br />
<br />
<table border="1 solid" colspa="0" cellspacing="0">
<tr>
<th>文章标题</th>
<th>分类</th>
<th>发表时间</th>
<th>状态</th>
<th>操作</th>
</tr>
<tr v-for="(article,index) in articleList">
<td>{{article.title}}</td>
<td>{{article.category}}</td>
<td>{{article.time}}</td>
<td>{{article.state}}</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
<!-- <tr>
<td>标题2</td>
<td>分类2</td>
<td>2000-01-01</td>
<td>已发布</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
<tr>
<td>标题3</td>
<td>分类3</td>
<td>2000-01-01</td>
<td>已发布</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr> -->
</table>
</div>
<!-- 导入axios的js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="module">
//导入vue模块
import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
//创建vue应用实例
createApp({
data(){
return {
articleList:[],
searchConditions:{
category:'',
state:''
}
}
},
methods:{
//声明方法
search:function(){
//发送请求,完成搜索,携带搜索条件
axios.get('http://localhost:8080/article/search?category='+this.searchConditions.category+'&state='+this.searchConditions.state)
.then(result=>{
//成功回调 result.data
//把得到的数据赋值给articleList
this.articleList=result.data
}).catch(err=>{
console.log(err);
});
}
},
//钩子函数mounted中,获取所有文章数据
mounted:function(){
//发送异步请求 axios
axios.get('http://localhost:8080/article/getAll').then(result=>{
//成功回调
//console.log(result.data);
this.articleList=result.data;
}).catch(err=>{
//失败回调
console.log(err);
});
}
}).mount('#app');//控制html元素
</script>
</body>
</html>
Vue工程化(整站)
环境准备
- 介绍:create-vue是Vue官方提供的最新的脚手架工具,用于快速生成一个工程化的Vue项目。
- create-vue提供了如下功能:
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包
- 依赖环境:NodeJS 【就是运行JavaScript代码的】。注意:Vue3要求NodeJS最低版本是16
nodeJS的安装
在Vue2 - 韩熙隐ario - 博客园已经安装了,这里可以跳过了。
此外可以配置npm的全局安装路径
使用管理员身份运行命令行,在命令行中,执行如下指令:
npm config set prefix "C:\Program Files\nodejs"
注意:C:\Program Files\nodejs 这个目录是NodeJS的安装目录
还可以更换安装包的源
这是因为npm资源实在国外的,所以改成镜像速度会快一点
npm:Node Package Manager,是NodeJS的软件包管理器。相当于maven
设置
npm config set registry http://registry.npm.taobao.org/
检查
npm config get registry
项目创建启动
项目创建
- 创建一个工程化的Vue项目,执行命令:npm init vue@latest
提示:执行上述指令,将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具【在vue2里面学的vue-cil先不用了】
现在都默认就行了,直接回车。
项目安装依赖
- 进入项目目录,执行命令安装当前项目的依赖:npm install
注意:创建项目以及安装依赖的过程,都是需要联网的。
然后输入code .命令,用vscode打开
目录结构
项目启动
启动有两种方式,
-
执行命令:npm run dev ,就可以启动vue项目了。
-
在Vscode中以图形化界面方式启动
访问项目:打开浏览器,在浏览器地址栏访问 http://127.0.0.1:5173 就可以访问到vue项目。
比Vue-cil有条理多了,也可能是比较老旧,版本冲突
Vue项目开发流程
先分析一下上面的首页。
首先主页很三个文件有关,分别是index.html,src/App.vue,src/main.js
可是显示的内容大部分是App.vue文件决定的,所以我们开发面向的主要是这个文件,而其他两个文件比较固定,自动生成。
Vue单文件组成
*.vue是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC,Single-File Components)。Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(*.vue)。
示例代码
<!-- Vue2风格 -->
<script>
//写数据
export default{
data(){
return {
msg:'上海'
}
}
}
</script>
<!-- Vue3风格,不用data函数而用ref定义响应式数据【就是知道双向绑定的数据】 -->
<!-- <script setup>
import {ref} from 'vue';
//调用ref函数,定义响应式数据
const msg = ref('Hello Vue3')
</script> -->
<template>
<h1>{{ msg }}</h1>
</template>
<style scoped>
h1{
color: #5d09ed;
}
</style>
效果图
API风格
-
Vue的组件有两种不同的风格:组合式API 和 选项式API
-
组合式API【推荐,符合编程习惯,灵活】
-
选项式API:【可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted等】
-
组合式API
- setup:是一个标识,告诉Vue需要进行一些处理,让我们可以更简洁的使用组合式API。
- ref():接收一个内部值,返回一个响应式的ref对象,此对象只有一个指向内部值的属性 value。
- onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后执行。
测试代码:
-
Api.vue
<script setup> import {onMounted, ref} from 'vue' //声明响应式数据ref,响应式对象有一个内部的属性value const count = ref(0); //注意不要丢掉ref函数,如果直接等于0就不是响应式数据了 //声明函数 function increment(){ //注意这里是.value。而不是count本身 count.value++; } //声明钩子函数 onMounted onMounted(()=>{ console.log('vue 已经挂载完毕了……') }) </script> <template> <!-- 写html元素 --> <button @click="increment">count:{{ count }}</button> </template>
-
写好了上面的Api.vue还显示不出来。要导入到App.vue中,并且在template中加入html代码
<script setup> import {ref} from 'vue'; //调用ref函数,定义响应式数据 const msg = ref('Hello Vue3') //导入Api.vue文件 import ApiVue from './Api.vue'; </script> <template> <h1>{{ msg }}</h1> <!-- 使用Api.vue文件 --> <ApiVue/> </template> <style scoped> h1{ color: #5d09ed; } </style>
案例
这里用工程化的方式实现局部使用时的案例
-
因为涉及到跨域问题,要首先安装axios,在项目目录中执行npm install axios
-
补充知识:...是扩展运算符可以把数组或者对象展开
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; console.log(combined); // 输出: [1, 2, 3, 4, 5, 6]
代码:同样这里也是要引入到App.vue中
<script setup>
//导入axios npm install axios
import axios from 'axios';
import {ref} from 'vue';
//定义响应式数据 ref,这里定义有个存数组,小括号写[]
const articleList = ref([])
//发送异步请求,获取所用文章数据。这里不用钩子函数是因为在这里是顺序执行的,天然能控制执行顺序
axios.get('http://localhost:8080/article/getAll')
.then(result=>{
//把服务器相应的数据保存起来
articleList.value = result.data;
}).catch(err=>{
console.log(err);
});
//定义响应式数据 searchConditions,这里定义结构化数据
const searchConditions = ref({
category:'',
state:''
})
//声明search函数,这里用一种实际开发中常用的方式
const search = function(){
//发送请求,完成搜索,...是扩展运算符可以把数组或者对象展开
axios.get('http://localhost:8080/article/search',{params:{...searchConditions.value}})
.then(result=>{
articleList.value = result.data;
}).catch(err=>{
console.log(err);
});
}
</script>
<template>
<div>
文章分类: <input type="text" v-model="searchConditions.category">
发布状态: <input type="text" v-model="searchConditions.state">
<button v-on:click="search">搜索</button>
<br />
<br />
<table border="1 solid" colspa="0" cellspacing="0">
<tr>
<th>文章标题</th>
<th>分类</th>
<th>发表时间</th>
<th>状态</th>
<th>操作</th>
</tr>
<tr v-for="(article,index) in articleList">
<td>{{article.title}}</td>
<td>{{article.category}}</td>
<td>{{article.time}}</td>
<td>{{article.state}}</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</table>
</div>
</template>
<style>
</style>
案例优化
-
如果以后很多地方都用到了获取所有文章数据和搜索功能,按照上述写法会造成很大的代码冗余。使开发中会把常用的接口所调用的js代码一般会封装到.js文件中, 并且以函数的形式暴露给外部
-
src/api/article.js文件
//导入axios npm install axios import axios from 'axios'; //获取所有文章数据的函数,这里一般用Service作为名称后缀,这不由得联想到了后端的service层 export function articleGetAllService() { //发送异步请求,获取所用文章数据。 axios.get('http://localhost:8080/article/getAll') .then(result=>{ //返回函数 return result.data; }).catch(err=>{ console.log(err); }); } //根据文章分类和发布状态搜索的函数,这里要传参了 export function articleSearchService(conditions) { //发送请求,完成搜索 axios.get('http://localhost:8080/article/search',{params:{conditions}}) .then(result=>{ return result.data; }).catch(err=>{ console.log(err); }); }
-
Article.vue文件【部分】
<script setup> //这里@等价于src import { articleGetAllService, articleSearchService } from '@/api/article'; import {ref} from 'vue'; //定义响应式数据 ref,这里定义有个存数组,小括号写[] const articleList = ref([]) //获取所用文章数据。 let data = articleGetAllService(); articleList.value = data; //定义响应式数据 searchConditions,这里定义结构化数据 const searchConditions = ref({ category:'', state:'' }) //声明search函数,这里用一种实际开发中常用的方式 const search = function(){ //搜索文章 let data = articleSearchService({...searchConditions.value}); articleList.value = data; } </script>
-
-
运行得知刷新不出来新东西。这是因为这里函数是异步的。没等到函数结束,主程序可能就已经执行完毕了。这里要同步等待服务器响应的结果。使用 async…await 同步接收网络请求的结果。await同步等待,但是await必须使用在异步函数【async修饰】中。记得异步返回return同步等待函数
-
形如这样
//获取所有文章数据的函数,这里一般用Service作为名称后缀,这不由得联想到了后端的service层 export async function articleGetAllService() { //发送异步请求,获取所用文章数据。 return await axios.get('http://localhost:8080/article/getAll') .then(result=>{ //返回函数 return result.data; }).catch(err=>{ console.log(err); }); } //根据文章分类和发布状态搜索的函数,这里要传参了 export async function articleSearchService(conditions) { //发送请求,完成搜索 return await axios.get('http://localhost:8080/article/search',{params:{conditions}}) .then(result=>{ return result.data; }).catch(err=>{ console.log(err); }); }
-
然后Article.vue中,也就意味着调用的是异步函数,要同步等待接受,用到await,但是await必须在async中,所以要专门在写应该函数。
//同步获取articleGetService的返回结果 async await const getAllArticle = async function () { let data = await articleGetAllService(); articleList.value = data; } //调用该函数 getAllArticle(); //声明search函数,这里不需要单独写函数,因为本身就是在函数中,直接加async就行。 const search = async function(){ //搜索文章 let data = await articleSearchService({...searchConditions.value}); articleList.value = data; }
-
-
观察请求地址可以发现。有共同前缀,这里可以定义一个常量区记录共同前缀,提高复用率
-
article.js
//导入axios npm install axios import axios from 'axios'; //定义一个变量,记录公共的前缀,baseURL const baseURL = 'http://localhost:8080'; //这里Instance和axios具有同样的功能 const instance = axios.create({baseURL}) //获取所有文章数据的函数,这里一般用Service作为名称后缀,这不由得联想到了后端的service层 export async function articleGetAllService() { //发送异步请求,获取所用文章数据。 return await instance.get('/article/getAll') .then(result=>{ //返回函数 return result.data; }).catch(err=>{ console.log(err); }); } //根据文章分类和发布状态搜索的函数,这里要传参了 export async function articleSearchService(conditions) { //发送请求,完成搜索 return await instance.get('/article/search',{params:{conditions}}) .then(result=>{ return result.data; }).catch(err=>{ console.log(err); }); }
-
-
我们发现这两部分代码重复度很高!
在实际开发中通常在request.js中定制request实例暴露给外界使用。在里面用到了拦截器【和和后端用到的拦截器道理是差不多的】
-
在请求或响应被 then 或 catch 处理前拦截它们
浏览器页面会和服务器发生多次交互。有过程请求和响应。如果在过个请求和响应中有共性的操作,这是就可以使用拦截器了。在请求发出之前添加请求拦截器,在响应到达之前添加响应拦截器。两种拦截器分别可以注册成功的回调和失败的回调。如果成功的话进行统一的配置请求头或者响应数据处理。如果失败了可以统一的处理请求错误,响应错误提示
在案例中主要涉及到了响应的优化,接下来主要操作响应拦截器,对于求情拦截器用到了再说。
代码实现:
一般情况下request.js会放在src/util目录下。对于先建立对应文件
-
request.js
//定制请求的实例 //导入axios npm install axios import axios from 'axios'; //定义一个变量,记录公共的前缀,baseURL const baseURL = 'http://localhost:8080'; //这里Instance和axios具有同样的功能 const instance = axios.create({baseURL}) //添加响应拦截器 instance.interceptors.response.use( result=>{ return result.data; }, err=>{ alert('服务异常'); return Promise.reject(err);//异步的状态转化成失败的状态 } ) //暴露接口 export default instance;
-
article.js。注意:这里就不用async await。因为拦截器本身就是异步的。如果不添加,这个异步会通过Article.vue中的await实现同步等待。所以依然能成功
import request from "@/util/request"; //获取所有文章数据的函数,这里一般用Service作为名称后缀,这不由得联想到了后端的service层 export function articleGetAllService() { //发送异步请求,获取所用文章数据。 return request.get('/article/getAll') } //根据文章分类和发布状态搜索的函数,这里要传参了 export function articleSearchService(conditions) { //发送请求,完成搜索 return request.get('/article/search',{params:{conditions}}) }
-
补充:
拦截器的作用
request.js
里的拦截器通常用来对请求和响应进行预处理与后处理。比如,在请求发出之前添加请求头、对请求参数进行处理;在响应返回之后对数据进行格式化、处理错误等。拦截器本身不会改变请求的异步性质,它只是在请求或响应的流程中插入了额外的逻辑。
await
和async
的作用
await
和async
是 ES2017 引入的语法糖,用于简化 Promise 的使用,让异步代码的编写更像同步代码。async
函数会返回一个 Promise 对象,而await
只能在async
函数里使用,它会暂停函数的执行,直至 Promise 被解决(resolved)或被拒绝(rejected)。
不使用
await
和async
的情况
在使用拦截器后,若拦截器已经处理了异步操作的结果,并且将处理后的数据以同步的方式返回,那么在Api.js
中就不需要再使用await
和async
了。例如,拦截器可以在响应拦截器里对数据进行处理,然后直接返回处理后的数据,而不是返回一个 Promise。
以下是一个简单的示例【重点看响应拦截器】:
// request.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
// 请求拦截器
instance.interceptors.request.use(config => {
// 在请求发出之前做些什么
config.headers.Authorization = 'Bearer token';
return config;
}, error => {
// 处理请求错误
console.error(error);
return Promise.reject(error);
});
// 响应拦截器
instance.interceptors.response.use(response => {
// 对响应数据做点什么
return response.data;
}, error => {
// 处理响应错误
console.error(error);
return Promise.reject(error);
});
export default instance;
// Api.js
import request from './request';
export const getData = () => {
return request.get('/data'); // 这里返回的是处理后的数据,不是Promise
};
在这个示例中,响应拦截器已经对响应数据进行了处理,直接返回了response.data
,所以在Api.js
中调用request.get
时,就不需要使用await
和async
了。
需要使用
await
和async
的情况
若拦截器只是对请求和响应进行了预处理和后处理,并没有改变请求的异步性质,那么在Api.js
中仍然需要使用await
和async
来处理异步操作。例如【重点看响应拦截器】:
// request.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
// 请求拦截器
instance.interceptors.request.use(config => {
// 在请求发出之前做些什么
config.headers.Authorization = 'Bearer token';
return config;
}, error => {
// 处理请求错误
console.error(error);
return Promise.reject(error);
});
// 响应拦截器
instance.interceptors.response.use(response => {
// 对响应数据做点什么
return response;
}, error => {
// 处理响应错误
console.error(error);
return Promise.reject(error);
});
export default instance;
// Api.js
import request from './request';
export const getData = async () => {
try {
const response = await request.get('/data');
return response.data;
} catch (error) {
console.error(error);
throw error;
}
};
在这个示例中,响应拦截器只是返回了response
,并没有对响应数据进行处理,所以在Api.js
中仍然需要使用await
和async
来处理异步操作。
Element Plus
简介
- Element:是饿了么团队研发的,基于 Vue 3,面向设计师和开发者的组件库。
- 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等。
- 官网:一个 Vue 3 UI 框架 | Element Plus
快速入门
准备工作
-
创建一个工程化的vue项目【和上面的创建Vue工程一模一样】,然后运行,保证创建成功
-
参照官方文档,安装Element Plus组件库(在当前工程的目录下):npm install element-plus --save
-
main.js中引入Element Plus组件库(参照官方文档),可以参考【快速开始】中引入【以完整引入为例】
//导入vue import { createApp } from 'vue' //导入element-plus import ElementPlus from 'element-plus' //导入element-plus的样式 import 'element-plus/dist/index.css' //导入app.vue import App from './App.vue' //创建应用实例 const app = createApp(App) //使用element-plus app.use(ElementPlus) //控制html元素 app.mount('#app')
制作组件:
-
访问Element官方文档,复制组件代码,调整
-
创建Button.vue,并把其导入到App.vue中。放在开发文档找到想要的 代码,添加的到文件中
-
然后可以利用浏览器工具,调整对应代码
-
此外还可以调整属性
-
常用组件
以实现这个效果图为例
主要分为一下四部分
表格
<script lang="ts" setup>
import {
Delete,
Edit
} from '@element-plus/icons-vue'
const tableData = [
{
title: '标题1',
category: '时事',
time: '2000-01-01',
state: '已发布'
},
{
title: '标题1',
category: '时事',
time: '2000-01-01',
state: '已发布'
},
{
title: '标题1',
category: '时事',
time: '2000-01-01',
state: '已发布'
},
{
title: '标题1',
category: '时事',
time: '2000-01-01',
state: '已发布'
},
{
title: '标题1',
category: '时事',
time: '2000-01-01',
state: '已发布'
}
]
</script>
<template>
<!-- 这里使用Vue中:绑定数据 -->
<el-table :data="tableData" style="width: 100%">
<!-- 这里使用prop属性进行绑定每一列数据 -->
<el-table-column prop="title" label="文章标题"/>
<el-table-column prop="category" label="分类"/>
<el-table-column prop="time" label="发表时间" />
<el-table-column prop="state" label="状态" />
<!-- 这里利用自动均分,让上面四个元素宽度相等。 -->
<el-table-column label="操作" width="180">
<div>
<el-button type="primary" :icon="Edit" circle />
<el-button type="danger" :icon="Delete" circle />
</div>
</el-table-column>
</el-table>
</template>
效果:
分页
对应代码较多时,常量定义和使用的举例会很大,可以按CTRL键,跳转到对应的位置
这里用的是Pagination分页All combined模块
其中默认是英文,可以在main.js文件中导入element准备的中文工具包
-
导入
//导入中文工具包 import locale from 'element-plus/dist/locale/zh-cn.js'
-
在user函数中传参
//使用element-plus app.use(ElementPlus, {locale})
效果:
表单
这里用行内表单组件
然后进行调整。其中的按钮和按钮组件一样,改变样式和对应的规则一样,是相通的。
卡片
这里使用card卡片中的基础用法
微调后发现不在一个卡片里面,可以直接不之前的代码写道卡片代码中。不同组件时可以混合使用的。然后再结合已经学的前端三要素,进行调整补充。
总代码
-
main.js文件
//导入vue import { createApp } from 'vue' //导入element-plus import ElementPlus from 'element-plus' //导入element-plus的样式 import 'element-plus/dist/index.css' //导入app.vue import App from './App.vue' //导入中文工具包 import locale from 'element-plus/dist/locale/zh-cn.js' //创建应用实例 const app = createApp(App) //使用element-plus app.use(ElementPlus, {locale}) //控制html元素 app.mount('#app')
-
Article.vue文件
<script lang="ts" setup> import { reactive } from 'vue' const formInline = reactive({ user: '', region: '', date: '', }) const onSubmit = () => { console.log('submit!') } import { ref } from 'vue' import type { ComponentSize } from 'element-plus' const total = ref(20) const currentPage4 = ref(4) const pageSize4 = ref(5) const size = ref<ComponentSize>('default') const background = ref(false) const disabled = ref(false) const handleSizeChange = (val: number) => { console.log(`${val} items per page`) } const handleCurrentChange = (val: number) => { console.log(`current page: ${val}`) } import { Delete, Edit } from '@element-plus/icons-vue' const tableData = [ { title: '标题1', category: '时事', time: '2000-01-01', state: '已发布' }, { title: '标题1', category: '时事', time: '2000-01-01', state: '已发布' }, { title: '标题1', category: '时事', time: '2000-01-01', state: '已发布' }, { title: '标题1', category: '时事', time: '2000-01-01', state: '已发布' }, { title: '标题1', category: '时事', time: '2000-01-01', state: '已发布' } ] </script> <template> <el-card> <div class="card-header"> <span>文章管理</span> <el-button type="primary">发布文章</el-button> </div> <div style="margin-top: 20px;"> <hr/> </div> <el-form :inline="true" :model="formInline" class="demo-form-inline"> <el-form-item label="文件分类:"> <el-select v-model="formInline.region" placeholder="请选择" clearable > <el-option label="时事" value="时事" /> <el-option label="风景" value="风景" /> </el-select> </el-form-item> <el-form-item label="发布状态:"> <el-select v-model="formInline.region" placeholder="请选择" clearable > <el-option label="已发布" value="已发布" /> <el-option label="草稿" value="草稿" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit">查询</el-button> </el-form-item> <el-form-item> <el-button type="default" @click="onSubmit">重置</el-button> </el-form-item> </el-form> <!-- 这里使用Vue中:绑定数据 --> <el-table :data="tableData" style="width: 100%"> <!-- 这里使用prop属性进行绑定每一列数据 --> <el-table-column prop="title" label="文章标题"/> <el-table-column prop="category" label="分类"/> <el-table-column prop="time" label="发表时间" /> <el-table-column prop="state" label="状态" /> <!-- 这里利用自动均分,让上面四个元素宽度相等。 --> <el-table-column label="操作" width="180"> <div> <el-button type="primary" :icon="Edit" circle /> <el-button type="danger" :icon="Delete" circle /> </div> </el-table-column> </el-table> <!-- 下面属性分别是:当前页面,页面大小,页面划分,size是尺寸,是否禁用,展示背景,布局,总数据数,两个单击事件 --> <el-pagination class="el-p" v-model:current-page="currentPage4" v-model:page-size="pageSize4" :page-sizes="[5, 10, 15, 20]" :size="size" :disabled="disabled" :background="background" layout="jumper, total, sizes, prev, pager, next" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </el-card> </template> <!-- scoped说明只在当前文件起作用 --> <style scoped> .el-p{ margin-top: 20px; display: flex; justify-content: flex-end; } .demo-form-inline .el-input { --el-input-width: 220px; } .demo-form-inline .el-select { --el-select-width: 220px; } .card-header{ /* 流式布局 */ display: flex; /* 两边对齐 */ justify-content: space-between; } </style>
-
效果图
Vue2和Vue3校验规则
非常好 👍
你这个想法很实用——很多人从 Vue 3 + Element Plus 回到 Vue 2 + Element UI 时,表单校验写法确实容易混淆。
我来帮你整理一份对照 + 简明示例 + 常见写法区别,格式直接适合你放进博客做笔记用。
Vue2 vs Vue3 表单校验对照笔记(Element UI & Element Plus)
一、基础概念对比
对比项 | Vue 2 + Element UI | Vue 3 + Element Plus |
---|---|---|
组件前缀 | <el-form> |
<el-form> (相同) |
模型绑定 | :model="form" |
:model="form" (相同) |
校验规则 | :rules="rules" |
:rules="rules" (相同) |
表单引用 | ref="form" + this.$refs.form |
ref="form" + formRef.value |
触发方式 | this.$refs.form.validate() |
formRef.value.validate() |
表单重置 | this.$refs.form.resetFields() |
formRef.value.resetFields() |
校验回调 | (valid) => { ... } |
Promise 风格:await formRef.value.validate() |
上面绑定的变量名不是固定的,只要和已有想用代码的对应即可
二、Vue 2 示例(Element UI)
<template>
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
form: {
username: "",
email: "",
},
// ✅ Vue2 写法的 rules
rules: {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
{ min: 2, max: 10, message: "长度在2~10个字符之间", trigger: "blur" },
],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{ type: "email", message: "邮箱格式不正确", trigger: ["blur", "change"] },
],
},
};
},
methods: {
submitForm() {
// Vue2 手动验证写法
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.$message.success("验证通过!");
} else {
this.$message.error("请填写完整表单!");
return false;
}
});
},
resetForm() {
this.$refs.ruleForm.resetFields();
},
},
};
</script>
三、Vue 3 示例(Element Plus)
<template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref } from "vue";
const formRef = ref();
const form = ref({
username: "",
email: "",
});
const rules = {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
{ min: 2, max: 10, message: "长度在2~10个字符之间", trigger: "blur" },
],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{ type: "email", message: "邮箱格式不正确", trigger: ["blur", "change"] },
],
};
const submitForm = async () => {
try {
await formRef.value.validate();
ElMessage.success("验证通过!");
} catch (error) {
ElMessage.error("请填写完整表单!");
}
};
const resetForm = () => {
formRef.value.resetFields();
};
</script>
四、主要区别总结
项目 | Vue 2 写法 | Vue 3 写法 |
---|---|---|
表单引用 | this.$refs.form |
formRef.value |
验证方法 | this.$refs.form.validate(cb) |
await formRef.value.validate() |
回调 vs Promise | 回调函数 | Promise / async-await |
重置方法 | this.$refs.form.resetFields() |
formRef.value.resetFields() |
rules 结构 | 相同 | 相同 |
触发方式 | "blur" / "change" |
"blur" / "change" (相同) |
五、常见错误提示与解决
错误现象 | 原因 | 解决办法 |
---|---|---|
点击提交无提示 | 忘记调用 .validate() |
手动添加验证调用 |
rules 无效 | 表单项 prop 未对应 model 字段 |
确保 prop 与 form 字段一致 |
重置不生效 | 没有设置 ref 或绑定错对象 |
检查 ref 名称一致性 |
自定义校验报错 | callback() 未调用 |
自定义校验中必须调用 callback() 或 Promise.resolve() |
六、自定义验证函数(Vue2 写法)
rules: {
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value.length < 6) {
callback(new Error("密码长度至少6位"));
} else {
callback();
}
},
trigger: "blur",
},
],
}