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)加载来自另一个源的资源,除非目标源明确允许这种跨源请求。

所有运行时不能直接从浏览器打开,要运行本土服务器

image

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/)

将数据渲染到前端界面

image

之所以说是渐进是框架,是因为一个项目可以局部使用Vue,也可以整站使用Vue

image

局部使用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>

image

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>

image

v-on
  • 作用:为html标签绑定事件
  • 语法:
    • v-on:事件名="函数名"
    • 简写为 @事件名="函数名"

createApp({ data(){需要用到的数据}, methods:{需要用到的方法} })

注意:函数需要定义在methods选项内部

<body>
    <div id="app">
        <button v-on:click="money">点我有惊喜</button> &nbsp;
        <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就指当前的示例对象。可以在函数中当前属性

声明周期

生命周期:指一个对象从创建到销毁的整个过程。
生命周期的八个阶段:每个阶段会自动执行一个生命周期方法(钩子), 让开发者有机会在特定的阶段执行自己的代码

下面的红色框就是钩子

image

状态 阶段周期
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使用步骤

  • 引入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后端程序。这样前后端就完美结合了。

案例

使用表格展示所有文章的数据, 并完成条件搜索功能

image

  • 钩子函数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先不用了】

image

现在都默认就行了,直接回车。

image

项目安装依赖

  • 进入项目目录,执行命令安装当前项目的依赖:npm install

注意:创建项目以及安装依赖的过程,都是需要联网的。

然后输入code .命令,用vscode打开

目录结构

image

项目启动

启动有两种方式,

  1. 执行命令:npm run dev ,就可以启动vue项目了。

  2. 在Vscode中以图形化界面方式启动

    image

访问项目:打开浏览器,在浏览器地址栏访问 http://127.0.0.1:5173 就可以访问到vue项目。

image

比Vue-cil有条理多了,也可能是比较老旧,版本冲突

Vue项目开发流程

先分析一下上面的首页。

首先主页很三个文件有关,分别是index.html,src/App.vue,src/main.js

image

可是显示的内容大部分是App.vue文件决定的,所以我们开发面向的主要是这个文件,而其他两个文件比较固定,自动生成。

Vue单文件组成

*.vue是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC,Single-File Components)。Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(*.vue)。

image

示例代码

<!-- 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>

效果图

image

API风格

  • Vue的组件有两种不同的风格:组合式API 和 选项式API

    1. 组合式API【推荐,符合编程习惯,灵活】

      image

    2. 选项式API:【可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted等】

      image

组合式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>
    

    image

案例

这里用工程化的方式实现局部使用时的案例

  • 因为涉及到跨域问题,要首先安装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>

案例优化

  1. 如果以后很多地方都用到了获取所有文章数据和搜索功能,按照上述写法会造成很大的代码冗余。使开发中会把常用的接口所调用的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>
      
  2. 运行得知刷新不出来新东西。这是因为这里函数是异步的。没等到函数结束,主程序可能就已经执行完毕了。这里要同步等待服务器响应的结果。使用 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;
      }
      
  3. 观察请求地址可以发现。有共同前缀,这里可以定义一个常量区记录共同前缀,提高复用率

    • 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);
              });
      }
      
  4. 我们发现这两部分代码重复度很高!
    image

    在实际开发中通常在request.js中定制request实例暴露给外界使用。在里面用到了拦截器【和和后端用到的拦截器道理是差不多的】

    • 在请求或响应被 then 或 catch 处理前拦截它们

      浏览器页面会和服务器发生多次交互。有过程请求和响应。如果在过个请求和响应中有共性的操作,这是就可以使用拦截器了。在请求发出之前添加请求拦截器,在响应到达之前添加响应拦截器。两种拦截器分别可以注册成功的回调和失败的回调。如果成功的话进行统一的配置请求头或者响应数据处理。如果失败了可以统一的处理请求错误,响应错误提示

      在案例中主要涉及到了响应的优化,接下来主要操作响应拦截器,对于求情拦截器用到了再说。

      image

    代码实现:

    一般情况下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里的拦截器通常用来对请求和响应进行预处理与后处理。比如,在请求发出之前添加请求头、对请求参数进行处理;在响应返回之后对数据进行格式化、处理错误等。拦截器本身不会改变请求的异步性质,它只是在请求或响应的流程中插入了额外的逻辑。

awaitasync的作用

awaitasync是 ES2017 引入的语法糖,用于简化 Promise 的使用,让异步代码的编写更像同步代码。async函数会返回一个 Promise 对象,而await只能在async函数里使用,它会暂停函数的执行,直至 Promise 被解决(resolved)或被拒绝(rejected)。

不使用awaitasync的情况

在使用拦截器后,若拦截器已经处理了异步操作的结果,并且将处理后的数据以同步的方式返回,那么在Api.js中就不需要再使用awaitasync了。例如,拦截器可以在响应拦截器里对数据进行处理,然后直接返回处理后的数据,而不是返回一个 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时,就不需要使用awaitasync了。

需要使用awaitasync的情况

若拦截器只是对请求和响应进行了预处理和后处理,并没有改变请求的异步性质,那么在Api.js中仍然需要使用awaitasync来处理异步操作。例如【重点看响应拦截器】:

// 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中仍然需要使用awaitasync来处理异步操作。

Element Plus

简介

  • Element:是饿了么团队研发的,基于 Vue 3,面向设计师和开发者的组件库。
  • 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等。
  • 官网:一个 Vue 3 UI 框架 | Element Plus

image

快速入门

准备工作

  1. 创建一个工程化的vue项目【和上面的创建Vue工程一模一样】,然后运行,保证创建成功

  2. 参照官方文档,安装Element Plus组件库(在当前工程的目录下):npm install element-plus --save

    image

  3. 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中。放在开发文档找到想要的 代码,添加的到文件中

      image

    • 然后可以利用浏览器工具,调整对应代码

      image

    • 此外还可以调整属性

      image

常用组件

以实现这个效果图为例

image

主要分为一下四部分

表格

<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>

效果:

image

分页

对应代码较多时,常量定义和使用的举例会很大,可以按CTRL键,跳转到对应的位置

这里用的是Pagination分页All combined模块

其中默认是英文,可以在main.js文件中导入element准备的中文工具包

  1. 导入

    //导入中文工具包
    import locale from 'element-plus/dist/locale/zh-cn.js'
    
  2. 在user函数中传参

    //使用element-plus
    app.use(ElementPlus, {locale})
    

效果:

image

表单

这里用行内表单组件

然后进行调整。其中的按钮和按钮组件一样,改变样式和对应的规则一样,是相通的。

卡片

这里使用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>
    
  • 效果图

    image

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 字段 确保 propform 字段一致
重置不生效 没有设置 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",
    },
  ],
}
posted @ 2025-04-09 22:50  韩熙隐ario  阅读(129)  评论(0)    收藏  举报