EN
/news/show.php/video/94296285.html

黑马 springboot3+vue3(大事件)笔记分享(前端)

2025-06-24 12:02:53 来源: 新华社
字号:默认 超大 | 打印 |

文章目录

  • 一、实战篇(前端)
    • 1、Vue
      • 1、快速入门
      • 2、常用指令
        • v-for
        • v-bind
        • v-if & v-show
        • v-on
        • v-model
      • 3、生命周期
        • Axios
        • 案例
    • 2、整站使用Vue(工程化)
      • 1、环境准备
      • 2、Vue项目创建和启动
      • 3、Vue项目开发流程
      • 4、API风格
      • 5、案例
    • 3、Element Plus
      • 1、快速入门
      • 2、常用组件
    • 4、大事件
      • 1. 环境准备
      • 2. 功能开发
        • 注册登录
        • 主页面布局
        • 路由
        • 子路由
        • 文章分类
        • Pinia状态管理库
        • 文章管理
        • 个人中心

一、实战篇(前端)

在这里插入图片描述
JavaScript-导入导出
在这里插入图片描述
在这里插入图片描述

JS提供的导入导出机制,可以实现按需导入。
在这里插入图片描述
或者
在这里插入图片描述

在这里插入图片描述
导入和导出的时候, 可以使用 as 重命名:如complexMessage as cm
默认导出
在这里插入图片描述
在这里插入图片描述

1、Vue

Vue 是一款用于构建用户界面的渐进式的JavaScript框架。 (官方:https://cn.vuejs.org/)
在这里插入图片描述
在这里插入图片描述
学习路径
在这里插入图片描述

1、快速入门

https://cn.vuejs.org
–准备
1、准备html页面,并引入Vue模块(官方提供)
2、创建Vue程序的应用实例
3、准备元素(div),被Vue控制
–构建用户界面
1、准备数据
2、通过插值表达式渲染页面

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app"><h1>{ { msg}}h1>div><div><h1>{ { msg}}h1>div><scripttype="module">import{ createApp}from'https://unpkg.com/vue@3/dist/vue.esm-browser.js';//引入Vue模块/* 创建vue的应用实例 */createApp({ //创建Vue程序的应用实例data(){ //准备数据return{ //定义数据msg:'hello vue3'}}}).mount("#app");script>body>html>

2、常用指令

指令:HTML标签上带有 v-前缀的特殊属性,不同的指令具有不同的含义,可以实现不同的功能。
常用指令:
在这里插入图片描述

v-for

在这里插入图片描述
作用:列表渲染,遍历容器的元素或者对象的属性
语法: v-for = “(item,index) in items”
–参数说明:
items 为遍历的数组
item 为遍历出来的元素
index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = “item in items”
注意:遍历的数组,必须在data中定义; 要想让哪个标签循环展示多次,就在哪个标签上使用 v-for 指令。

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app"><tableborder="1 solid"colspa="0"cellspacing="0"><tr><th>文章标题th><th>分类th><th>发表时间th><th>状态th><th>操作th>tr><trv-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><scripttype="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:属性名=“属性值”
简化::属性名=“属性值”
注意:v-bind所绑定的数据,必须在data中定义 。

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app"><a:href="url">黑马官网a>div><scripttype="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来控制显示与隐藏
场景:频繁切换显示隐藏的场景

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app">手链价格为:  <spanv-if="customer.level>=0 && customer.level<=1">9.9span><spanv-else-if="customer.level>=2 && customer.level<=4">19.9span><spanv-else>29.9span><br/>手链价格为:  <spanv-show="customer.level>=0 && customer.level<=1">9.9span><spanv-show="customer.level>=2 && customer.level<=4">19.9span><spanv-show="customer.level>=5">29.9span>div><scripttype="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:{ 需要用到的方法} })

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app"><buttonv-on:click="money">点我有惊喜button> <button@click="love">再点更惊喜button>div><scripttype="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>html>
v-model

作用:在表单元素上使用,双向数据绑定。可以方便的 获取 或 设置 表单项数据
语法:v-model=“变量名”
在这里插入图片描述
在这里插入图片描述
注意:v-model 中绑定的变量,必须在data中定义。

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app">文章分类: <inputtype="text"v-model="searchConditions.category"/><span>{ { searchConditions.category}}span>发布状态: <inputtype="text"v-model="searchConditions.state"/><span>{ { searchConditions.state}}span><button>搜索button><buttonv-on:click="clear">重置button><br/><br/><tableborder="1 solid"colspa="0"cellspacing="0"><tr><th>文章标题th><th>分类th><th>发表时间th><th>状态th><th>操作th>tr><trv-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><scripttype="module">//导入vue模块import{ createApp }from'https://unpkg.com/vue@3/dist/vue.esm-browser.js'//创建vue应用实例createApp({ data(){ return{ //定义数据searchConditions:{ category:'',state:''},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:"已发布"}]}},methods:{ clear:function(){ //清空category以及state的数据//在methods对应的方法里面,使用this就代表的是vue实例,可以使用this获取到vue实例中准备的数据this.searchConditions.category='';this.searchConditions.state='';}},mounted:function(){ console.log('Vue挂载完毕,发送请求获取数据')}}).mount("#app")//控制html元素script>body>html>

3、生命周期

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

Axios

介绍:Axios 对原生的Ajax进行了封装,简化书写,快速开发。
官网:https://www.axios-http.cn/

Axios使用步骤
引入Axios的js文件(参照官网)
使用Axios发送请求,并获取相应结果
在这里插入图片描述

Axios-请求方式别名
为了方便起见,Axios已经为所有支持的请求方法提供了别名
格式:axios.请求方式(url [, data [, config]])
在这里插入图片描述

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><scriptsrc="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);        }); */letarticle ={ 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>
案例

使用表格展示所有文章的数据, 并完成条件搜索功能
在这里插入图片描述
钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜索按钮绑定单击事件

DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><divid="app">文章分类: <inputtype="text"v-model="searchConditions.category">发布状态: <inputtype="text"v-model="searchConditions.state"><buttonv-on:click="search">搜索button><br/><br/><tableborder="1 solid"colspa="0"cellspacing="0"><tr><th>文章标题th><th>分类th><th>发表时间th><th>状态th><th>操作th>tr><trv-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><scriptsrc="https://unpkg.com/axios/dist/axios.min.js">script><scripttype="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//把得到的数据赋值给articleListthis.articleList=result.data                    }).catch(err=>{ console.log(err);});}},//钩子函数mounted中,获取所有文章数据mounted:function(){ //发送异步请求  axiosaxios.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>

2、整站使用Vue(工程化)

1、环境准备

在这里插入图片描述

在这里插入图片描述

  1. 选择安装目录

选择安装到一个,没有中文,没有空格的目录下(新建一个文件夹NodeJS)


在这里插入图片描述

  1. 验证NodeJS环境变量

NodeJS 安装完毕后,会自动配置好环境变量,我们验证一下是否安装成功,通过: node -v

在这里插入图片描述

  1. 配置npm的全局安装路径

在这里插入图片描述

使用管理员身份运行命令行,在命令行中,执行如下指令:

npm config set prefix "D:\develop\NodeJS"

注意:D:\develop\NodeJS 这个目录是NodeJS的安装目录

5.更换安装包的源

设置

npm config set registry http://registry.npm.taobao.org/

检查

npm config get registry

2、Vue项目创建和启动

创建一个工程化的Vue项目,执行命令:npm init vue@latest
在这里插入图片描述
在这里插入图片描述
进入项目目录,执行命令安装当前项目的依赖:npm install
在这里插入图片描述
Vue项目-目录结构
在这里插入图片描述
Vue项目-启动
执行命令:npm run dev ,就可以启动vue项目了。
在这里插入图片描述
或者
在这里插入图片描述
访问项目:打开浏览器,在浏览器地址栏访问 http://127.0.0.1:5173 就可以访问到vue项目。
在这里插入图片描述

3、Vue项目开发流程

Vue项目-目录结构
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
.vue是Vue项目中的组件文件,在Vue项目中也称为单文件组件(SFC,Single-File Components)。Vue 的单文件组件会将一个组件的逻辑 (JS),模板 (HTML) 和样式 (CSS) 封装在同一个文件里(.vue) 。

<scriptsetup>import{ ref}from'vue';//调用ref函数,定义响应式数据constmsg =ref('西安');//导入 Api.vue文件importApiVue from'./Api.vue'//导入Article.vue文件importArticleVue from'./Article.vue'script><template><ArticleVue/>template><stylescoped>/* 样式 */h1{ color:red;}style>

4、API风格

Vue的组件有两种不同的风格:组合式API 和 选项式API
在这里插入图片描述
setup:是一个标识,告诉Vue需要进行一些处理,让我们可以更简洁的使用组合式API。
ref():接收一个内部值,返回一个响应式的ref对象,此对象只有一个指向内部值的属性 value。
onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后执行。

<scriptsetup>import{ ref,onMounted}from'vue'//声明响应式数据 ref  响应式对象有一个内部的属性valueconstcount =ref(0);//在组合式api中,一般需要把数据定义为响应式数据//const count=0;//声明函数functionincrement(){ count.value++;}//声明钩子函数 onMountedonMounted(()=>{ console.log('vue 已经挂载完毕了...');});script><template><button@click="increment">count: { {  count }}button>template>

在这里插入图片描述
选项式API,可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted等。

5、案例

使用表格展示所有文章的数据, 并完成条件搜索功能
在这里插入图片描述
钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜索按钮绑定单击事件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在请求或响应被 then 或 catch 处理前拦截它们
在这里插入图片描述
Artcle.vue

<scriptsetup>import{ articleGetAllService,articleSearchService}from'@/api/article.js';import{ ref}from'vue';//定义响应式数据  refconstarticleList =ref([]);//获取所有文章数据//同步获取articleGetAllService的返回结果  async awaitconstgetAllArticle=asyncfunction(){ letdata =awaitarticleGetAllService();articleList.value=data;}getAllArticle();//定义响应式数据 searchConditionsconstsearchConditions =ref({ category:'',state:''})//声明search函数constsearch=asyncfunction(){ //文章搜索letdata =awaitarticleSearchService({ ...searchConditions.value});articleList.value =data;}script><template><div>文章分类: <inputtype="text"v-model="searchConditions.category">发布状态: <inputtype="text"v-model="searchConditions.state"><buttonv-on:click="search">搜索button><br/><br/><tableborder="1 solid"colspa="0"cellspacing="0"><tr><th>文章标题th><th>分类th><th>发表时间th><th>状态th><th>操作th>tr><trv-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>

artcle.js

/* //导入axios  npm install axiosimport axios from 'axios';//定义一个变量,记录公共的前缀  ,  baseURLconst baseURL = 'http://localhost:8080';const instance = axios.create({ baseURL}) */importrequest from'@/util/request.js'exportfunctionarticleGetAllService(){ returnrequest.get('/article/getAll');}exportfunctionarticleSearchService(conditions){ returnrequest.get('/article/search',{ params:conditions });}

request.js

//定制请求的实例//导入axios  npm install axiosimportaxios from'axios';//定义一个变量,记录公共的前缀  ,  baseURLconstbaseURL ='http://localhost:8080';constinstance =axios.create({ baseURL})//添加响应拦截器instance.interceptors.response.use(result=>{ returnresult.data;},err=>{ alert('服务异常');returnPromise.reject(err);//异步的状态转化成失败的状态})exportdefaultinstance;

3、Element Plus

Element:是饿了么团队研发的,基于 Vue 3,面向设计师和开发者的组件库。
组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等。
官网:https://element-plus.org/zh-CN/#/zh-CN

1、快速入门

准备工作:
创建一个工程化的vue项目
参照官方文档,安装Element Plus组件库(在当前工程的目录下):npm install element-plus --save
在这里插入图片描述

main.js中引入Element Plus组件库(参照官方文档)

import{ createApp }from'vue'//导入vueimportElementPlus from'element-plus'//导入element-plusimport'element-plus/dist/index.css'//导入element-plus的样式importApp from'./App.vue'//导入app.vueimportlocale from'element-plus/dist/locale/zh-cn.js'constapp =createApp(App)//创建应用实例app.use(ElementPlus,{ locale})//使用element-plusapp.mount('#app')//控制html元素

制作组件:
访问Element官方文档,复制组件代码,调整
Button.vue

<scriptlang="ts"setup>import{ Check,Delete,Edit,Message,Search,Star,}from'@element-plus/icons-vue'script><template><el-rowclass="mb-4"><el-button>Defaultel-button><el-buttontype="primary"disabled="true">编辑el-button><el-buttontype="success"loading="true">查看el-button>el-row><el-rowclass="mb-4"><el-buttontype="info"plain>Infoel-button><el-buttontype="warning"plain>Warningel-button><el-buttontype="danger"plain>Dangerel-button>el-row>template>

App.vue

<scriptsetup>importButtonVue from'./Button.vue'importArticleVue from'./Article.vue'script><template><ArticleVue/>template>

2、常用组件

在这里插入图片描述
Article.vue

<scriptlang="ts"setup>import{ reactive }from'vue'constformInline =reactive({ user:'',region:'',date:'',})constonSubmit=()=>{ console.log('submit!')}import{ ref }from'vue'constcurrentPage4 =ref(2)constpageSize4 =ref(5)constsmall =ref(false)constbackground =ref(false)constdisabled =ref(false)consttotal =ref(20)consthandleSizeChange=(val:number)=>{ console.log(`${ val}items per page`)}consthandleCurrentChange=(val:number)=>{ console.log(`current page: ${ val}`)}import{ Delete,Edit,}from'@element-plus/icons-vue'consttableData =[{ 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-cardclass="box-card"><divclass="card-header"><span>文章管理span><el-buttontype="primary">发布文章el-button>div><divstyle="margin-top:20px;"><hr>div><el-form:inline="true":model="formInline"class="demo-form-inline"><el-form-itemlabel="文章分类:"><el-selectv-model="formInline.region"placeholder="请选择"clearable><el-optionlabel="时事"value="时事"/><el-optionlabel="篮球"value="篮球"/>el-select>el-form-item><el-form-itemlabel="发布状态:"><el-selectv-model="formInline.region"placeholder="请选择"clearable><el-optionlabel="已发布"value="已发布"/><el-optionlabel="草稿"value="草稿"/>el-select>el-form-item><el-form-item><el-buttontype="primary"@click="onSubmit">搜索el-button>el-form-item><el-form-item><el-buttontype="default"@click="onSubmit">重置el-button>el-form-item>el-form><el-table:data="tableData"style="width:100%"><el-table-columnprop="title"label="文章标题"/><el-table-columnprop="category"label="分类"/><el-table-columnprop="time"label="发表时间"/><el-table-columnprop="state"label="状态"/><el-table-columnlabel="操作"width="180"><el-row><el-buttontype="primary":icon="Edit"circle/><el-buttontype="danger":icon="Delete"circle/>el-row>el-table-column>el-table><el-paginationclass="el-p"v-model:current-page="currentPage4"v-model:page-size="pageSize4":page-sizes="[5, 10, 15, 20]":small="small":disabled="disabled":background="background"layout="jumper,total, sizes, prev, pager, next":total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"/>el-card>template><stylescoped>.el-p{ margin-top:20px;display:flex;justify-content:flex-end;}.card-header{ display:flex;justify-content:space-between;}style>

4、大事件

需求在这里插入图片描述

1. 环境准备

1.创建Vue工程
npm init vue@latest
2. 安装依赖
Element-Plus
npm install element-plus --save
Axios
npm install axios
Sass
npm install sass -D
3. 目录调整
4. 删除components下面自动生成的内容
新建目录api、utils、views
将资料中的静态资源拷贝到assets目录下
删除App.uve中自动生成的内容

在这里插入图片描述
在这里插入图片描述
main.js

import'./assets/main.scss'import{ createApp }from'vue'importElementPlus from'element-plus'import'element-plus/dist/index.css'importrouter from'@/router'importApp from'./App.vue'import{ createPinia}from'pinia'import{ createPersistedState }from'pinia-persistedstate-plugin'importlocale from'element-plus/dist/locale/zh-cn.js'constapp =createApp(App);constpinia =createPinia();constpersist =createPersistedState();pinia.use(persist)app.use(pinia)app.use(router)app.use(ElementPlus,{ locale});app.mount('#app')

2. 功能开发

注册登录

在这里插入图片描述
搭建页面
数据绑定:参考接口文档给属性起名
表单校验:
el-form标签上通过rules属性,绑定校验规则
el-form-item标签上通过prop属性,指定校验项

Login.vue

<scriptsetup>import{ User,Lock }from'@element-plus/icons-vue'import{ ref }from'vue'import{ ElMessage }from'element-plus'//控制注册与登录表单的显示, 默认显示注册constisRegister =ref(false)//定义数据模型constregisterData =ref({ username:'',password:'',rePassword:''})//校验密码的函数constcheckRePassword=(rule,value,callback)=>{ if(value ===''){ callback(newError('请再次确认密码'))}elseif(value !==registerData.value.password){ callback(newError('请确保两次输入的密码一样'))}else{ callback()}}//定义表单校验规则construles ={ username:[{ required:true,message:'请输入用户名',trigger:'blur'},{ min:5,max:16,message:'长度为5~16位非空字符',trigger:'blur'}],password:[{ required:true,message:'请输入密码',trigger:'blur'},{ min:5,max:16,message:'长度为5~16位非空字符',trigger:'blur'}],rePassword:[{ validator:checkRePassword,trigger:'blur'}]}//调用后台接口,完成注册import{ userRegisterService,userLoginService}from'@/api/user.js'constregister=async()=>{ //registerData是一个响应式对象,如果要获取值,需要.valueletresult =awaituserRegisterService(registerData.value);/* if (result.code === 0) {         //成功了        alert(result.msg ? result.msg : '注册成功');    }else{         //失败了        alert('注册失败')    } *///alert(result.msg ? result.msg : '注册成功');ElMessage.success(result.msg ?result.msg :'注册成功')}//绑定数据,复用注册表单的数据模型//表单数据校验//登录函数import{ useTokenStore}from'@/stores/token.js'import{ useRouter}from'vue-router'constrouter =useRouter()consttokenStore =useTokenStore();constlogin=async()=>{ //调用接口,完成登录letresult =awaituserLoginService(registerData.value);/* if(result.code===0){     alert(result.msg? result.msg : '登录成功')   }else{     alert('登录失败')   } *///alert(result.msg? result.msg : '登录成功')ElMessage.success(result.msg ?result.msg :'登录成功')//把得到的token存储到pinia中tokenStore.setToken(result.data)//跳转到首页 路由完成跳转router.push('/')}//定义函数,清空数据模型的数据constclearRegisterData=()=>{ registerData.value={ username:'',password:'',rePassword:''}}script><template><el-rowclass="login-page"><el-col:span="12"class="bg">el-col><el-col:span="6":offset="3"class="form"><el-formref="form"size="large"autocomplete="off"v-if="isRegister":model="registerData":rules="rules"><el-form-item><h1>注册h1>el-form-item><el-form-itemprop="username"><el-input:prefix-icon="User"placeholder="请输入用户名"v-model="registerData.username">el-input>el-form-item><el-form-itemprop="password"><el-input:prefix-icon="Lock"type="password"placeholder="请输入密码"v-model="registerData.password">el-input>el-form-item><el-form-itemprop="rePassword"><el-input:prefix-icon="Lock"type="password"placeholder="请输入再次密码"v-model="registerData.rePassword">el-input>el-form-item><el-form-item><el-buttonclass="button"type="primary"auto-insert-space@click="register">注册                    el-button>el-form-item><el-form-itemclass="flex"><el-linktype="info":underline="false"@click="isRegister = false;clearRegisterData()">← 返回                    el-link>el-form-item>el-form><el-formref="form"size="large"autocomplete="off"v-else:model="registerData":rules="rules"><el-form-item><h1>登录h1>el-form-item><el-form-itemprop="username"><el-input:prefix-icon="User"placeholder="请输入用户名"v-model="registerData.username">el-input>el-form-item><el-form-itemprop="password"><el-inputname="password":prefix-icon="Lock"type="password"placeholder="请输入密码"v-model="registerData.password">el-input>el-form-item><el-form-itemclass="flex"><divclass="flex"><el-checkbox>记住我el-checkbox><el-linktype="primary":underline="false">忘记密码?el-link>div>el-form-item><el-form-item><el-buttonclass="button"type="primary"auto-insert-space@click="login">登录el-button>el-form-item><el-form-itemclass="flex"><el-linktype="info":underline="false"@click="isRegister = true;clearRegisterData()">注册 →                    el-link>el-form-item>el-form>el-col>el-row>template><stylelang="scss"scoped>/* 样式 */.login-page{ height:100vh;background-color:#fff;.bg{ background:url('@/assets/logo2.png')no-repeat 60% center / 240px auto,url('@/assets/login_bg.jpg')no-repeat center / cover;border-radius:0 20px 20px 0;}.form{ display:flex;flex-direction:column;justify-content:center;user-select:none;.title{ margin:0 auto;}.button{ width:100%;}.flex{ width:100%;display:flex;justify-content:space-between;}}}style>

api/user.js

//导入request.js请求工具importrequest from'@/utils/request.js'//提供调用注册接口的函数exportconstuserRegisterService=(registerData)=>{ //借助于UrlSearchParams完成传递constparams =newURLSearchParams()for(letkey inregisterData){ params.append(key,registerData[key]);}returnrequest.post('/user/register',params);}//提供调用登录接口的函数exportconstuserLoginService=(loginData)=>{ constparams =newURLSearchParams();for(letkey inloginData){ params.append(key,loginData[key])}returnrequest.post('/user/login',params)}//获取用户详细信息exportconstuserInfoService=()=>{ returnrequest.get('/user/userInfo')}//修改个人信息exportconstuserInfoUpdateService=(userInfoData)=>{ returnrequest.put('/user/update',userInfoData)}//修改头像exportconstuserAvatarUpdateService=(avatarUrl)=>{ constparams =newURLSearchParams();params.append('avatarUrl',avatarUrl)returnrequest.patch('/user/updateAvatar',params)}

跨域
由于浏览器的同源策略限制,向不同源(不同协议、不同域名、不同端口)发送ajax请求会失败
在这里插入图片描述
优化axios响应拦截器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

request.js

//定制请求的实例//导入axios  npm install axiosimportaxios from'axios';import{ ElMessage }from'element-plus'//定义一个变量,记录公共的前缀  ,  baseURL//const baseURL = 'http://localhost:8080';constbaseURL ='/api';constinstance =axios.create({ baseURL })import{ useTokenStore}from'@/stores/token.js'//添加请求拦截器instance.interceptors.request.use((config)=>{ //请求前的回调//添加tokenconsttokenStore =useTokenStore();//判断有没有tokenif(tokenStore.token){ config.headers.Authorization =tokenStore.token        }returnconfig;},(err)=>{ //请求错误的回调Promise.reject(err)})/* import { useRouter} from 'vue-router'const router = useRouter(); */importrouter from'@/router'//添加响应拦截器instance.interceptors.response.use(result=>{ //判断业务状态码if(result.data.code===0){ returnresult.data;}//操作失败//alert(result.data.msg?result.data.msg:'服务异常')ElMessage.error(result.data.msg?result.data.msg:'服务异常')//异步操作的状态转换为失败returnPromise.reject(result.data)},err=>{ //判断响应状态码,如果为401,则证明未登录,提示请登录,并跳转到登录页面if(err.response.status===401){ ElMessage.error('请先登录')router.push('/login')}else{ ElMessage.error('服务异常')}returnPromise.reject(err);//异步的状态转化成失败的状态})exportdefaultinstance;

vite.config.js

import{ fileURLToPath,URL}from'node:url'import{ defineConfig }from'vite'importvue from'@vitejs/plugin-vue'importpath from'node:path'// https://vitejs.dev/config/exportdefaultdefineConfig({ plugins:[vue(),],resolve:{ alias:{ '@':fileURLToPath(newURL('./src',import.meta.url))}},server:{ proxy:{ '/api':{ //获取路径中包含了/api的请求target:'http://localhost:8080',//后台服务所在的源changeOrigin:true,//修改源rewrite:(path)=>path.replace(/^\/api/,'')///api替换为''}}}})
主页面布局

在这里插入图片描述
Layout.vue

<scriptsetup>import{ Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom}from'@element-plus/icons-vue'importavatar from'@/assets/default.png'import{ userInfoService}from'@/api/user.js'importuseUserInfoStore from'@/stores/userInfo.js'import{ useTokenStore}from'@/stores/token.js'consttokenStore =useTokenStore();constuserInfoStore =useUserInfoStore();//调用函数,获取用户详细信息constgetUserInfo=async()=>{ //调用接口letresult =awaituserInfoService();//数据存储到pinia中userInfoStore.setInfo(result.data);}getUserInfo();//条目被点击后,调用的函数import{ useRouter}from'vue-router'constrouter =useRouter();import{ ElMessage,ElMessageBox}from'element-plus'consthandleCommand=(command)=>{ //判断指令if(command ==='logout'){ //退出登录ElMessageBox.confirm('您确认要退出吗?','温馨提示',{ confirmButtonText:'确认',cancelButtonText:'取消',type:'warning',}).then(async()=>{ //退出登录//1.清空pinia中存储的token以及个人信息tokenStore.removeToken()userInfoStore.removeInfo()//2.跳转到登录页面router.push('/login')ElMessage({ type:'success',message:'退出登录成功',})}).catch(()=>{ ElMessage({ type:'info',message:'用户取消了退出登录',})})}else{ //路由router.push('/user/'+command)}}script><template><el-containerclass="layout-container"><el-asidewidth="200px"><divclass="el-aside__logo">div><el-menuactive-text-color="#ffd04b"background-color="#232323"text-color="#fff"router><el-menu-itemindex="/article/category"><el-icon><Management/>el-icon><span>文章分类span>el-menu-item><el-menu-itemindex="/article/manage"><el-icon><Promotion/>el-icon><span>文章管理span>el-menu-item><el-sub-menu><template#title><el-icon><UserFilled/>el-icon><span>个人中心span>template><el-menu-itemindex="/user/info"><el-icon><User/>el-icon><span>基本资料span>el-menu-item><el-menu-itemindex="/user/avatar"><el-icon><Crop/>el-icon><span>更换头像span>el-menu-item><el-menu-itemindex="/user/resetPassword"><el-icon><EditPen/>el-icon><span>重置密码span>el-menu-item>el-sub-menu>el-menu>el-aside><el-container><el-header><div>黑马程序员:<strong>{ {  userInfoStore.info.nickname }}strong>div><el-dropdownplacement="bottom-end"@command="handleCommand"><spanclass="el-dropdown__box"><el-avatar:src="userInfoStore.info.userPic? userInfoStore.info.userPic:avatar"/><el-icon><CaretBottom/>el-icon>span><template#dropdown><el-dropdown-menu><el-dropdown-itemcommand="info":icon="User">基本资料el-dropdown-item><el-dropdown-itemcommand="avatar":icon="Crop">更换头像el-dropdown-item><el-dropdown-itemcommand="resetPassword":icon="EditPen">重置密码el-dropdown-item><el-dropdown-itemcommand="logout":icon="SwitchButton">退出登录el-dropdown-item>el-dropdown-menu>template>el-dropdown>el-header><el-main><router-view>router-view>el-main><el-footer>大事件 ©2023 Created by 黑马程序员el-footer>el-container>el-container>template><stylelang="scss"scoped>.layout-container{ height:100vh;.el-aside{ background-color:#232323;&__logo{ height:120px;background:url('@/assets/logo.png')no-repeat center / 120px auto;}.el-menu{ border-right:none;}}.el-header{ background-color:#fff;display:flex;align-items:center;justify-content:space-between;.el-dropdown__box{ display:flex;align-items:center;.el-icon{ color:#999;margin-left:10px;}&:active,            &:focus{ outline:none;}}}.el-footer{ display:flex;align-items:center;justify-content:center;font-size:14px;color:#666;}}style>
路由

路由,决定从起点到终点的路径的进程
在前端工程中,路由指的是根据不同的访问路径,展示不同组件的内容
Vue Router是Vue.js的官方路由
Vue Router
安装vue-router npm install vue-router@4
在src/router/index.js中创建路由器,并导出
在vue应用实例中使用vue-router
声明router-view标签,展示组件内容
在这里插入图片描述
App.vue
在这里插入图片描述
index.js
在这里插入图片描述

子路由

在这里插入图片描述
复制资料中提供好的五个组件
配置子路由
声明router-view标签
为菜单项 el-menu-item 设置index属性,设置点击后的路由路径
在这里插入图片描述

index.js

import{ createRouter,createWebHistory }from'vue-router'//导入组件importLoginVue from'@/views/Login.vue'importLayoutVue from'@/views/Layout.vue'importArticleCategoryVue from'@/views/article/ArticleCategory.vue'importArticleManageVue from'@/views/article/ArticleManage.vue'importUserAvatarVue from'@/views/user/UserAvatar.vue'importUserInfoVue from'@/views/user/UserInfo.vue'importUserResetPasswordVue from'@/views/user/UserResetPassword.vue'//定义路由关系constroutes =[{ path:'/login',component:LoginVue },{ path:'/',component:LayoutVue,redirect:'/article/manage',children:[{ path:'/article/category',component:ArticleCategoryVue },{ path:'/article/manage',component:ArticleManageVue },{ path:'/user/info',component:UserInfoVue },{ path:'/user/avatar',component:UserAvatarVue },{ path:'/user/resetPassword',component:UserResetPasswordVue }]}]//创建路由器constrouter =createRouter({ history:createWebHistory(),routes:routes})//导出路由exportdefaultrouter
文章分类

在这里插入图片描述
api/article.js

importrequest from'@/utils/request.js'import{ useTokenStore }from'@/stores/token.js'//文章分类列表查询exportconstarticleCategoryListService=()=>{ //const tokenStore = useTokenStore();//在pinia中定义的响应式数据,都不需要.value//return request.get('/category',{ headers:{ 'Authorization':tokenStore.token}})returnrequest.get('/category')}//文章分类添加exportconstarticleCategoryAddService=(categoryData)=>{ returnrequest.post('/category',categoryData)}//文章分类修改exportconstarticleCategoryUpdateService=(categoryData)=>{ returnrequest.put('/category',categoryData)}//文章分类删除exportconstarticleCategoryDeleteService=(id)=>{ returnrequest.delete('/category?id='+id)}//文章列表查询exportconstarticleListService=(params)=>{ returnrequest.get('/article',{ params:params})}//文章添加exportconstarticleAddService=(articleData)=>{ returnrequest.post('/article',articleData);}

AticleCategory.vue

<scriptsetup>import{ Edit,Delete}from'@element-plus/icons-vue'import{ ref }from'vue'constcategorys =ref([{ "id":3,"categoryName":"美食","categoryAlias":"my","createTime":"2023-09-02 12:06:59","updateTime":"2023-09-02 12:06:59"},{ "id":4,"categoryName":"娱乐","categoryAlias":"yl","createTime":"2023-09-02 12:08:16","updateTime":"2023-09-02 12:08:16"},{ "id":5,"categoryName":"军事","categoryAlias":"js","createTime":"2023-09-02 12:08:33","updateTime":"2023-09-02 12:08:33"}])//声明一个异步的函数import{ articleCategoryListService,articleCategoryAddService,articleCategoryUpdateService,articleCategoryDeleteService }from'@/api/article.js'constarticleCategoryList=async()=>{ letresult =awaitarticleCategoryListService();categorys.value =result.data;}articleCategoryList();//控制添加分类弹窗constdialogVisible =ref(false)//添加分类数据模型constcategoryModel =ref({ categoryName:'',categoryAlias:''})//添加分类表单校验construles ={ categoryName:[{ required:true,message:'请输入分类名称',trigger:'blur'},],categoryAlias:[{ required:true,message:'请输入分类别名',trigger:'blur'},]}//调用接口,添加表单import{ ElMessage }from'element-plus'constaddCategory=async()=>{ //调用接口letresult =awaitarticleCategoryAddService(categoryModel.value);ElMessage.success(result.msg ?result.msg :'添加成功')//调用获取所有文章分类的函数articleCategoryList();dialogVisible.value =false;}//定义变量,控制标题的展示consttitle =ref('')//展示编辑弹窗constshowDialog=(row)=>{ dialogVisible.value =true;title.value ='编辑分类'//数据拷贝categoryModel.value.categoryName =row.categoryName;categoryModel.value.categoryAlias =row.categoryAlias;//扩展id属性,将来需要传递给后台,完成分类的修改categoryModel.value.id =row.id}//编辑分类constupdateCategory=async()=>{ //调用接口letresult =awaitarticleCategoryUpdateService(categoryModel.value);ElMessage.success(result.msg ?result.msg :'修改成功')//调用获取所有分类的函数articleCategoryList();//隐藏弹窗dialogVisible.value =false;}//清空模型的数据constclearData=()=>{ categoryModel.value.categoryName ='';categoryModel.value.categoryAlias ='';}//删除分类import{ ElMessageBox}from'element-plus'constdeleteCategory=(row)=>{ //提示用户  确认框ElMessageBox.confirm('你确认要删除该分类信息吗?','温馨提示',{ confirmButtonText:'确认',cancelButtonText:'取消',type:'warning',}).then(async()=>{ //调用接口letresult =awaitarticleCategoryDeleteService(row.id);ElMessage({ type:'success',message:'删除成功',})//刷新列表articleCategoryList();}).catch(()=>{ ElMessage({ type:'info',message:'用户取消了删除',})})}script><template><el-cardclass="page-container"><template#header><divclass="header"><span>文章分类span><divclass="extra"><el-buttontype="primary"@click="dialogVisible = true; title = '添加分类'; clearData()">添加分类el-button>div>div>template><el-table:data="categorys"style="width:100%"><el-table-columnlabel="序号"width="100"type="index">el-table-column><el-table-columnlabel="分类名称"prop="categoryName">el-table-column><el-table-columnlabel="分类别名"prop="categoryAlias">el-table-column><el-table-columnlabel="操作"width="100"><template#default="{  row }"><el-button:icon="Edit"circleplaintype="primary"@click="showDialog(row)">el-button><el-button:icon="Delete"circleplaintype="danger"@click="deleteCategory(row)">el-button>template>el-table-column><template#empty><el-emptydescription="没有数据"/>template>el-table><el-dialogv-model="dialogVisible":title="title"width="30%"><el-form:model="categoryModel":rules="rules"label-width="100px"style="padding-right:30px"><el-form-itemlabel="分类名称"prop="categoryName"><el-inputv-model="categoryModel.categoryName"minlength="1"maxlength="10">el-input>el-form-item><el-form-itemlabel="分类别名"prop="categoryAlias"><el-inputv-model="categoryModel.categoryAlias"minlength="1"maxlength="15">el-input>el-form-item>el-form><template#footer><spanclass="dialog-footer"><el-button@click="dialogVisible = false">取消el-button><el-buttontype="primary"@click="title == '添加分类' ? addCategory() : updateCategory()">确认 el-button>span>template>el-dialog>el-card>template><stylelang="scss"scoped>.page-container{ min-height:100%;box-sizing:border-box;.header{ display:flex;align-items:center;justify-content:space-between;}}style>
Pinia状态管理库

Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态
在这里插入图片描述
安装pinia npm install pinia
在vue应用实例中使用pinia
在src/stores/token.js中定义store
在组件中使用store
在这里插入图片描述
在这里插入图片描述

token.js
在这里插入图片描述
Axios请求拦截器
在这里插入图片描述
在这里插入图片描述
Pinia持久化插件-persist
Pinia默认是内存存储,当刷新浏览器的时候会丢失数据。
Persist插件可以将pinia中的数据持久化的存储

安装persist npm install pinia-persistedstate-plugin
在pinia中使用persist
定义状态Store时指定持久化配置参数

main.js
在这里插入图片描述
在这里插入图片描述
未登录统一处理
在这里插入图片描述

文章管理

添加文章分类
在这里插入图片描述
添加分类弹窗页面

<!--添加分类弹窗 --><el-dialog v-model="dialogVisible"title="添加弹层"width="30%"><el-form :model="categoryModel":rules="rules"label-width="100px"style="padding-right: 30px"><el-form-item label="分类名称"prop="categoryName"><el-input v-model="categoryModel.categoryName"minlength="1"maxlength="10"></el-input></el-form-item><el-form-item label="分类别名"prop="categoryAlias"><el-input v-model="categoryModel.categoryAlias"minlength="1"maxlength="15"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary">确认 </el-button></span></template></el-dialog>

数据模型和校验规则

//控制添加分类弹窗constdialogVisible =ref(false)//添加分类数据模型constcategoryModel =ref({ categoryName:'',categoryAlias:''})//添加分类表单校验construles ={ categoryName:[{ required:true,message:'请输入分类名称',trigger:'blur'},],categoryAlias:[{ required:true,message:'请输入分类别名',trigger:'blur'},]}

添加分类按钮单击事件

添加分类

接口调用

在article.js中提供添加分类的函数

//添加文章分类exportconstarticleCategoryAddService=(categoryModel)=>{ returnrequest.post('/category',categoryModel)}

在页面中调用接口

//访问后台,添加文章分类constaddCategory=async()=>{ letresult =awaitarticleCategoryAddService(categoryModel.value);ElMessage.success(result.message?result.message:'添加成功')//隐藏弹窗dialogVisible.value =false//再次访问后台接口,查询所有分类getAllCategory()}<el-button type="primary"@click="addCategory">确认 </el-button>

修改文章分类
在这里插入图片描述
修改分类弹窗页面

修改分类弹窗和新增文章分类弹窗长的一样,所以可以服用添加分类的弹窗

弹窗标题显示定义标题

//弹窗标题const title=ref('')

在弹窗上绑定标题

为添加分类按钮绑定事件

添加分类

为修改分类按钮绑定事件

<el-button :icon="Edit"circle plain type="primary"@click="title='修改分类';dialogVisible=true"></el-button>

数据回显

当点击修改分类按钮时,需要把当前这一条数据的详细信息显示到修改分类的弹窗上,这个叫回显

通过插槽的方式得到被点击按钮所在行的数据

<template #default="{  row }"><el-button :icon="Edit"circle plain type="primary"@click="updateCategoryEcho(row)"></el-button><el-button :icon="Delete"circle plain type="danger"></el-button></template>

回显函数

//修改分类回显constupdateCategoryEcho=(row)=>{ title.value ='修改分类'dialogVisible.value =true//将row中的数据赋值给categoryModelcategoryModel.value.categoryName=row.categoryName        categoryModel.value.categoryAlias=row.categoryAlias        //修改的时候必须传递分类的id,所以扩展一个id属性categoryModel.value.id=row.id    }

接口调用

article.js中提供修改分类的函数

//修改分类exportconstarticleCategoryUpdateService=(categoryModel)=>{ returnrequest.put('/category',categoryModel)}

修改确定按钮的绑定事件

<span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary"@click="title==='添加分类'? addCategory():updateCategory()">确认 </el-button></span>

调用接口完成修改的函数

//修改分类constupdateCategory=async()=>{ letresult =awaitarticleCategoryUpdateService(categoryModel.value)ElMessage.success(result.message?result.message:'修改成功')//隐藏弹窗dialogVisible.value=false//再次访问后台接口,查询所有分类getAllCategory()}

由于现在修改和新增共用了一个数据模型,所以在点击添加分类后,有时候会显示数据,此时可以将categoryModel中的数据清空

//清空模型数据constclearCategoryModel=()=>{ categoryModel.value.categoryName='',categoryModel.value.categoryAlias=''}

修改添加按钮的点击事件

<el-button type="primary"@click="title = '添加分类'; dialogVisible = true;clearCategoryModel()">添加分类</el-button>

删除文章分类
在这里插入图片描述
确认框

//删除分类  给删除按钮绑定事件constdeleteCategory=(row)=>{ ElMessageBox.confirm('你确认删除该分类信息吗?','温馨提示',{ confirmButtonText:'确认',cancelButtonText:'取消',type:'warning',}).then(()=>{ //用户点击了确认ElMessage({ type:'success',message:'删除成功',})}).catch(()=>{ //用户点击了取消ElMessage({ type:'info',message:'取消删除',})})}

接口调用

article.js中提供删除分类的函数

//删除分类exportconstarticleCategoryDeleteService=(id)=>{ returnrequest.delete('/category?id='+id)}

当用户点击确认后,调用接口删除分类

//删除分类constdeleteCategory=(row)=>{ ElMessageBox.confirm('你确认删除该分类信息吗?','温馨提示',{ confirmButtonText:'确认',cancelButtonText:'取消',type:'warning',}).then(async()=>{ //用户点击了确认letresult =awaitarticleCategoryDeleteService(row.id)ElMessage.success(result.message?result.message:'删除成功')//再次调用getAllCategory,获取所有文章分类getAllCategory()}).catch(()=>{ //用户点击了取消ElMessage({ type:'info',message:'取消删除',})})}

文章列表查询
在这里插入图片描述
文章列表页面组件

<scriptsetup>import{ Edit,Delete    }from'@element-plus/icons-vue'import{ ref }from'vue'//文章分类数据模型constcategorys =ref([{ "id":3,"categoryName":"美食","categoryAlias":"my","createTime":"2023-09-02 12:06:59","updateTime":"2023-09-02 12:06:59"},{ "id":4,"categoryName":"娱乐","categoryAlias":"yl","createTime":"2023-09-02 12:08:16","updateTime":"2023-09-02 12:08:16"},{ "id":5,"categoryName":"军事","categoryAlias":"js","createTime":"2023-09-02 12:08:33","updateTime":"2023-09-02 12:08:33"}])//用户搜索时选中的分类idconstcategoryId=ref('')//用户搜索时选中的发布状态conststate=ref('')//文章列表数据模型constarticles =ref([{ "id":5,"title":"陕西旅游攻略","content":"兵马俑,华清池,法门寺,华山...爱去哪去哪...","coverImg":"https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png","state":"草稿","categoryId":2,"createTime":"2023-09-03 11:55:30","updateTime":"2023-09-03 11:55:30"},{ "id":5,"title":"陕西旅游攻略","content":"兵马俑,华清池,法门寺,华山...爱去哪去哪...","coverImg":"https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png","state":"草稿","categoryId":2,"createTime":"2023-09-03 11:55:30","updateTime":"2023-09-03 11:55:30"},{ "id":5,"title":"陕西旅游攻略","content":"兵马俑,华清池,法门寺,华山...爱去哪去哪...","coverImg":"https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png","state":"草稿","categoryId":2,"createTime":"2023-09-03 11:55:30","updateTime":"2023-09-03 11:55:30"},])//分页条数据模型constpageNum =ref(1)//当前页consttotal =ref(20)//总条数constpageSize =ref(3)//每页条数//当每页条数发生了变化,调用此函数constonSizeChange=(size)=>{ pageSize.value =size    }//当前页码发生变化,调用此函数constonCurrentChange=(num)=>{ pageNum.value =num    }script><template><el-cardclass="page-container"><template#header><divclass="header"><span>文章管理span><divclass="extra"><el-buttontype="primary">添加文章el-button>div>div>template><el-forminline><el-form-itemlabel="文章分类:"><el-selectplaceholder="请选择"v-model="categoryId"><el-optionv-for="c in categorys":key="c.id":label="c.categoryName":value="c.id">el-option>el-select>el-form-item><el-form-itemlabel="发布状态:"><el-selectplaceholder="请选择"v-model="state"><el-optionlabel="已发布"value="已发布">el-option><el-optionlabel="草稿"value="草稿">el-option>el-select>el-form-item><el-form-item><el-buttontype="primary">搜索el-button><el-button>重置el-button>el-form-item>el-form><el-table:data="articles"style="width:100%"><el-table-columnlabel="文章标题"width="400"prop="title">el-table-column><el-table-columnlabel="分类"prop="categoryId">el-table-column><el-table-columnlabel="发表时间"prop="createTime">el-table-column><el-table-columnlabel="状态"prop="state">el-table-column><el-table-columnlabel="操作"width="100"><template#default="{  row }"><el-button:icon="Edit"circleplaintype="primary">el-button><el-button:icon="Delete"circleplaintype="danger">el-button>template>el-table-column><template#empty><el-emptydescription="没有数据"/>template>el-table><el-paginationv-model:current-page="pageNum"v-model:page-size="pageSize":page-sizes="[3, 5 ,10, 15]"layout="jumper, total, sizes, prev, pager, next"background:total="total"@size-change="onSizeChange"@current-change="onCurrentChange"style="margin-top:20px;justify-content:flex-end"/>el-card>template><stylelang="scss"scoped>.page-container{ min-height:100%;box-sizing:border-box;.header{ display:flex;align-items:center;justify-content:space-between;}}style>

使用中文语言包,解决分页条中文问题, 在main.js中完成

importlocale from'element-plus/dist/locale/zh-cn.js'app.use(ElementPlus,{ locale})

文章分类数据回显

ArticleMange.vue

//文章列表查询import{ articleCategoryListService }from'@/api/article.js'constgetArticleCategoryList=async()=>{ //获取所有分类letresultC =awaitarticleCategoryListService();categorys.value =resultC.data}getArticleCategoryList();

文章列表接口调用

article.js中提供获取文章列表数据的函数

//文章列表查询exportconstarticleListService=(params)=>{ returnrequest.get('/article',{ params:params })}

ArticleManage.vue中,调用接口获取数据

//文章列表查询import{ articleListService }from'@/api/article.js'constgetArticles=async()=>{ letparams ={ pageNum:pageNum.value,pageSize:pageSize.value,categoryId:categoryId.value ?categoryId.value :null,state:state.value ?state.value :null}letresult =awaitarticleListService(params);//渲染列表数据articles.value =result.data.items        //为列表中添加categoryName属性for(leti=0;i<articles.value.length;i++){ letarticle =articles.value[i];for(letj=0;j<categorys.value.length;j++){ if(article.categoryId===categorys.value[j].id){ article.categoryName=categorys.value[j].categoryName                }}}//渲染总条数total.value=result.data.total    }getArticles()

当分页条的当前页和每页条数发生变化,重新发送请求获取数据

//当每页条数发生了变化,调用此函数constonSizeChange=(size)=>{ pageSize.value =size    getArticles()}//当前页码发生变化,调用此函数constonCurrentChange=(num)=>{ pageNum.value =num    getArticles()}

搜索和重置

为搜索按钮绑定单击事件,调用getArticles函数即可

搜索

为重置按钮绑定单击事件,清除categoryId和state的之即可

重置

添加文章
在这里插入图片描述
添加文章抽屉组件

import { Plus} from '@element-plus/icons-vue'//控制抽屉是否显示const visibleDrawer = ref(false)//添加表单数据模型const articleModel = ref({     title: '',    categoryId: '',    coverImg: '',    content:'',    state:''})<el-drawerv-model="visibleDrawer"title="添加文章"direction="rtl"size="50%"><el-form:model="articleModel"label-width="100px"><el-form-itemlabel="文章标题"><el-inputv-model="articleModel.title"placeholder="请输入标题">el-input>el-form-item><el-form-itemlabel="文章分类"><el-selectplaceholder="请选择"v-model="articleModel.categoryId"><el-optionv-for="c in categorys":key="c.id":label="c.categoryName":value="c.id">el-option>el-select>el-form-item><el-form-itemlabel="文章封面"><el-uploadclass="avatar-uploader":auto-upload="false":show-file-list="false"><imgv-if="articleModel.coverImg":src="articleModel.coverImg"class="avatar"/><el-iconv-elseclass="avatar-uploader-icon"><Plus/>el-icon>el-upload>el-form-item><el-form-itemlabel="文章内容"><divclass="editor">富文本编辑器div>el-form-item><el-form-item><el-buttontype="primary">发布el-button><el-buttontype="info">草稿el-button>el-form-item>el-form>el-drawer>/* 抽屉样式 */.avatar-uploader {     :deep() {         .avatar {             width: 178px;            height: 178px;            display: block;        }        .el-upload {             border: 1px dashed var(--el-border-color);            border-radius: 6px;            cursor: pointer;            position: relative;            overflow: hidden;            transition: var(--el-transition-duration-fast);        }        .el-upload:hover {             border-color: var(--el-color-primary);        }        .el-icon.avatar-uploader-icon {             font-size: 28px;            color: #8c939d;            width: 178px;            height: 178px;            text-align: center;        }    }}.editor {   width: 100%;  :deep(.ql-editor) {     min-height: 200px;  }}

为添加文章按钮添加单击事件,展示抽屉

添加文章

富文本编辑器

文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill

官网地址: https://vueup.github.io/vue-quill/

安装:

npm install @vueup/vue-quill@latest --save

导入组件和样式:

import{ QuillEditor }from'@vueup/vue-quill'import'@vueup/vue-quill/dist/vue-quill.snow.css'

页面长使用quill组件:

<quill-editor              theme="snow"v-model:content="articleModel.content"contentType="html"></quill-editor>

样式美化:

.editor { width:100%;:deep(.ql-editor){ min-height:200px;}}

文章封面图片上传

将来当点击+图标,选择本地图片后,el-upload这个组件会自动发送请求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步请求,所以需要给el-upload标签添加一些属性,控制请求的发送

auto-upload:是否自动上传

action: 服务器接口路径

name: 上传的文件字段名

headers: 设置上传的请求头

on-success: 上传成功的回调函数

import{ Plus    }from'@element-plus/icons-vue'<el-form-item label="文章封面"><el-upload class="avatar-uploader":show-file-list="false"><img v-if="articleModel.coverImg":src="articleModel.coverImg"class="avatar"/><el-icon v-elseclass="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item>

注意:

  1. 由于这个请求时el-upload自动发送的异步请求,并没有使用咱们的request.js请求工具,所以在请求的路ing上,需要加上/api, 这个时候请求代理才能拦截到这个请求,转发到后台服务器上

  2. 要携带请求头,还需要导入pinia状态才可以使用

import{ useTokenStore }from'@/stores/token.js'consttokenStore =useTokenStore();
  1. 在成功的回调函数中,可以拿到服务器响应的数据,其中有一个属性为data,对应的就是图片在阿里云oss上存储的访问地址,需要把它赋值给articleModel的coverImg属性,这样img标签就能显示这张图片了,因为img标签上通过src属性绑定了articleModel.coverImg
//上传图片成功回调constuploadSuccess=(img)=>{ //img就是后台响应的数据,格式为:{ code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data      }

添加文章接口调用

article.js中提供添加文章函数

//添加文章exportconstarticleAddService=(articleModel)=>{ returnrequest.post('/article',articleModel)}

为已发布和草稿按钮绑定事件

<el-form-item><el-buttontype="primary"@click="addArticle('已发布')">发布el-button><el-buttontype="info"@click="addArticle('草稿')">草稿el-button>el-form-item>

ArticleManage.vue中提供addArticle函数完成添加文章接口的调用

//添加文章constaddArticle=async(state)=>{ articleModel.value.state =state        letresult =awaitarticleAddService(articleModel.value);ElMessage.success(result.message?result.message:'添加成功')//再次调用getArticles,获取文章getArticles()//隐藏抽屉visibleDrawer.value=false}

顶部导航栏信息显示
在这里插入图片描述
在Layout.vue中,页面加载完就发送请求,获取个人信息展示,并存储到pinia中,因为将来在个人中心中修改信息的时候还需要使用

user.js中提供获取个人信息的函数

//获取个人信息exportconstuserInfoGetService=()=>{ returnrequest.get('/user/userInfo');}

src/stores/user.js中,定义个人中心状态

import{ defineStore }from"pinia"import{ ref}from'vue'exportconstuseUserInfoStore =defineStore('userInfo',()=>{ //1.定义用户信息constinfo =ref({ })//2.定义修改用户信息的方法constsetInfo=(newInfo)=>{ info.value =newInfo        }//3.定义清空用户信息的方法constremoveInfo=()=>{ info.value={ }}return{ info,setInfo,removeInfo}},{ persist:true})

Layout.vue中获取个人信息,并存储到pinia中

//导入接口函数import{ userInfoGetService}from'@/api/user.js'//导入piniaimport{ useUserInfoStore}from'@/stores/user.js'constuserInfoStore =useUserInfoStore();import{ ref}from'vue'//获取个人信息constgetUserInf=async()=>{ letresult =awaituserInfoGetService();//存储piniauserInfoStore.info =result.data;}getUserInf()

Layout.vue的顶部导航栏中,展示昵称和头像

<div>黑马程序员:<strong>{ {  userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}strong>div><el-avatar:src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar"/>

下拉菜单功能
在这里插入图片描述
el-dropdown中功能实现

在el-dropdown中有四个子条目,分别是:

  • 基本资料
  • 更换头像
  • 重置密码
  • 退出登录

其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo

路由实现:

在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持一致

<el-dropdown-menu><el-dropdown-itemcommand="info":icon="User">基本资料el-dropdown-item><el-dropdown-itemcommand="avatar":icon="Crop">更换头像el-dropdown-item><el-dropdown-itemcommand="password":icon="EditPen">重置密码el-dropdown-item><el-dropdown-itemcommand="logout":icon="SwitchButton">退出登录el-dropdown-item>el-dropdown-menu>

在el-dropdown标签上绑定command事件,当有条目被点击后,会触发这个事件

提供handleCommand函数,参数为点击条目的command属性值

//dropDown条目被点击后,回调的函数import{ useRouter}from'vue-router'constrouter =useRouter()consthandleCommand=(command)=>{ if(command==='logout'){ //退出登录alert('退出登录')}else{ //路由router.push('/user/'+command)}}

退出登录实现:

import{ ElMessage,ElMessageBox}from'element-plus'import{ useTokenStore }from'@/stores/token.js'consttokenStore =useTokenStore()consthandleCommand=(command)=>{ if(command ==='logout'){ //退出登录ElMessageBox.confirm('你确认退出登录码?','温馨提示',{ confirmButtonText:'确认',cancelButtonText:'取消',type:'warning',}).then(async()=>{ //用户点击了确认//清空pinia中的token和个人信息userInfoStore.info={ }tokenStore.token=''//跳转到登录页router.push('/login')}).catch(()=>{ //用户点击了取消ElMessage({ type:'info',message:'取消退出',})})}else{ //路由router.push('/user/'+command)}}
个人中心

基本资料修改
在这里插入图片描述
基本资料页面组件

<scriptsetup>import{ ref }from'vue'constuserInfo =ref({ id:0,username:'zhangsan',nickname:'zs',email:'[email protected]',})construles ={ nickname:[{ required:true,message:'请输入用户昵称',trigger:'blur'},{ pattern:/^\S{ 2,10}$/,message:'昵称必须是2-10位的非空字符串',trigger:'blur'}],email:[{ required:true,message:'请输入用户邮箱',trigger:'blur'},{ type:'email',message:'邮箱格式不正确',trigger:'blur'}]}script><template><el-cardclass="page-container"><template#header><divclass="header"><span>基本资料span>div>template><el-row><el-col:span="12"><el-form:model="userInfo":rules="rules"label-width="100px"size="large"><el-form-itemlabel="登录名称"><el-inputv-model="userInfo.username"disabled>el-input>el-form-item><el-form-itemlabel="用户昵称"prop="nickname"><el-inputv-model="userInfo.nickname">el-input>el-form-item><el-form-itemlabel="用户邮箱"prop="email"><el-inputv-model="userInfo.email">el-input>el-form-item><el-form-item><el-buttontype="primary">提交修改el-button>el-form-item>el-form>el-col>el-row>el-card>template>

表单数据回显

个人信息之前已经存储到了pinia中,只需要从pinia中获取个人信息,替换模板数据即可

import{ useUserInfoStore }from'@/stores/user.js';constuserInfoStore =useUserInfoStore()constuserInfo =ref({ ...userInfoStore.info})

接口调用

在src/api/user.js中提供修改基本资料的函数

//修改个人信息exportconstuserInfoUpdateService=(userInfo)=>{ returnrequest.put('/user/update',userInfo)}

为修改按钮绑定单击事件

提交修改

提供updateUserInfo函数

//修改用户信息import{ userInfoUpdateService}from'@/api/user.js'import{ ElMessage }from'element-plus';constupdateUserInfo=async()=>{ letresult =awaituserInfoUpdateService(userInfo.value)ElMessage.success(result.message?result.message:'修改成功')//更新pinia中的数据userInfoStore.info.nickname=userInfo.value.nickname        userInfoStore.info.email =userInfo.value.email    }

用户头像修改
在这里插入图片描述
修改头像页面组件

<scriptsetup>import{ Plus,Upload }from'@element-plus/icons-vue'import{ ref}from'vue'importavatar from'@/assets/default.png'constuploadRef =ref()//用户头像地址constimgUrl=avatarscript><template><el-cardclass="page-container"><template#header><divclass="header"><span>更换头像span>div>template><el-row><el-col:span="12"><el-uploadref="uploadRef"class="avatar-uploader":show-file-list="false"><imgv-if="imgUrl":src="imgUrl"class="avatar"/><imgv-elsesrc="avatar"width="278"/>el-upload><br/><el-buttontype="primary":icon="Plus"size="large"@click="uploadRef.$el.querySelector('input').click()">选择图片                el-button><el-buttontype="success":icon="Upload"size="large">上传头像                el-button>el-col>el-row>el-card>template><stylelang="scss"scoped>.avatar-uploader{ :deep(){ .avatar{ width:278px;height:278px;display:block;}.el-upload{ border:1px dashed var(--el-border-color);border-radius:6px;cursor:pointer;position:relative;overflow:hidden;transition:var(--el-transition-duration-fast);}.el-upload:hover{ border-color:var(--el-color-primary);}.el-icon.avatar-uploader-icon{ font-size:28px;color:#8c939d;width:278px;height:278px;text-align:center;}}}style>

头像回显

从pinia中读取用户的头像数据

//读取用户信息import{ ref}from'vue'import{ useUserInfoStore}from'@/stores/user.js'constuserInfoStore =useUserInfoStore()constimgUrl=ref(userInfoStore.info.userPic)

img标签上绑定图片地址

<img v-if="imgUrl":src="imgUrl"class="avatar"/><img v-elsesrc="@/assets/avatar.jpg"width="278"/>

头像上传

为el-upload指定属性值,分别有:

  • ​ action: 服务器接口路径
  • ​ headers: 设置请求头,需要携带token
  • ​ on-success: 上传成功的回调函数
  • ​ name: 上传图片的字段名称
<el-uploadclass="avatar-uploader":show-file-list="false":auto-upload="true"action="/api/upload"name="file":headers="{ 'Authorization':tokenStore.token}":on-success="uploadSuccess"><imgv-if="imgUrl":src="imgUrl"class="avatar"/><imgv-elsesrc="@/assets/avatar.jpg"width="278"/>el-upload>

提供上传成功的回调函数

//读取token信息import{ useTokenStore}from'@/stores/token.js'consttokenStore =useTokenStore()//图片上传成功的回调constuploadSuccess=(result)=>{ //回显图片imgUrl.value =result.data    }

外部触发图片选择

​ 需要获取到el-upload组件,然后再通过$el.querySelector(‘input’)获取到el-upload对应的元素,触发click事件

//获取el-upload元素constuploadRef =ref()<el-button type="primary":icon="Plus"size="large"@click="uploadRef.$el.querySelector('input').click()">选择图片    </el-button>

接口调用

在user.js中提供修改头像的函数

//修改头像exportconstuserAvatarUpdateService=(avatarUrl)=>{ letparams =newURLSearchParams();params.append('avatarUrl',avatarUrl)returnrequest.patch('/user/updateAvatar',params)}

为【上传头像】按钮绑定单击事件

<el-button type="success":icon="Upload"size="large"@click="updateAvatar">上传头像</el-button>

提供updateAvatar函数,完成头像更新

//调用接口,更新头像urlimport{ userAvatarUpdateService}from'@/api/user.js'import{ ElMessage}from'element-plus'constupdateAvatar=async()=>{ letresult =awaituserAvatarUpdateService(imgUrl.value)ElMessage.success(result.message?result.message:'修改成功')//更新pinia中的数据userInfoStore.info.userPic=imgUrl.value}

【我要纠错】责任编辑:新华社