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

打印 上一主题 下一主题

主题 1583|帖子 1583|积分 4749

一、实战篇(前端)


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、通过插值表达式渲染页面
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app"><!-- 准备元素(div),被Vue控制 -->
  10.         <h1>{{msg}}</h1><!-- 通过插值表达式渲染页面 -->
  11.     </div>
  12.     <div >
  13.         <h1>{{msg}}</h1>
  14.     </div>
  15.     <!-- 引入vue模块 -->
  16.     <script type="module">
  17.         import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';//引入Vue模块
  18.         /* 创建vue的应用实例 */
  19.         createApp({//创建Vue程序的应用实例
  20.             data(){//准备数据
  21.                 return {
  22.                     //定义数据
  23.                     msg: 'hello vue3'
  24.                 }
  25.             }
  26.         }).mount("#app");
  27.     </script>
  28. </body>
  29. </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 指令。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         <table border="1 solid" colspa="0" cellspacing="0">
  11.             <tr>
  12.                 <th>文章标题</th>
  13.                 <th>分类</th>
  14.                 <th>发表时间</th>
  15.                 <th>状态</th>
  16.                 <th>操作</th>
  17.             </tr>
  18.             <!-- 哪个元素要出现多次,v-for指令就添加到哪个元素上 -->
  19.             <tr v-for="(article,index) in articleList">
  20.                 <td>{{article.title}}</td>
  21.                 <td>{{article.category}}</td>
  22.                 <td>{{article.time}}</td>
  23.                 <td>{{article.state}}</td>
  24.                 <td>
  25.                     <button>编辑</button>
  26.                     <button>删除</button>
  27.                 </td>
  28.             </tr>
  29.             <!-- <tr>
  30.                 <td>标题2</td>
  31.                 <td>分类2</td>
  32.                 <td>2000-01-01</td>
  33.                 <td>已发布</td>
  34.                 <td>
  35.                     <button>编辑</button>
  36.                     <button>删除</button>
  37.                 </td>
  38.             </tr>
  39.             <tr>
  40.                 <td>标题3</td>
  41.                 <td>分类3</td>
  42.                 <td>2000-01-01</td>
  43.                 <td>已发布</td>
  44.                 <td>
  45.                     <button>编辑</button>
  46.                     <button>删除</button>
  47.                 </td>
  48.             </tr> -->
  49.         </table>
  50.     </div>
  51.     <script type="module">
  52.         //导入vue模块
  53.         import { createApp} from
  54.                 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  55.         //创建应用实例
  56.         createApp({
  57.             data() {
  58.                 return {
  59.                   //定义数据
  60.                     articleList:[{
  61.                                 title:"医疗反腐绝非砍医护收入",
  62.                                 category:"时事",
  63.                                 time:"2023-09-5",
  64.                                 state:"已发布"
  65.                             },
  66.                             {
  67.                                 title:"中国男篮缘何一败涂地?",
  68.                                 category:"篮球",
  69.                                 time:"2023-09-5",
  70.                                 state:"草稿"
  71.                             },
  72.                             {
  73.                                 title:"华山景区已受大风影响阵风达7-8级,未来24小时将持续",
  74.                                 category:"旅游",
  75.                                 time:"2023-09-5",
  76.                                 state:"已发布"
  77.                             }]  
  78.                 }
  79.             }
  80.         }).mount("#app")//控制页面元素
  81.         
  82.     </script>
  83. </body>
  84. </html>
复制代码
v-bind

作用:动态为HTML标签绑定属性值,如设置href,src,style样式等。
语法:v-bind:属性名=“属性值”
简化::属性名=“属性值”
注意:v-bind所绑定的数据,必须在data中定义 。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         <!-- <a v-bind:href="url">黑马官网</a> -->
  11.         <a :href="url">黑马官网</a>
  12.     </div>
  13.     <script type="module">
  14.         //引入vue模块
  15.         import { createApp} from
  16.                 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  17.         //创建vue应用实例
  18.         createApp({
  19.             data() {
  20.                 return {
  21.                     url: 'https://www.itheima.com'
  22.                 }
  23.             }
  24.         }).mount("#app")//控制html元素
  25.     </script>
  26. </body>
  27. </html>
复制代码
v-if & v-show

作用:这两类指令,都是用来控制元素的显示与隐藏的
v-if
语法:v-if=“表达式”,表达式值为 true,显示;false,隐藏
别的:可以共同 v-else-if / v-else 举行链式调用条件判断
原理:基于条件判断,来控制创建或移除元素节点(条件渲染)
场景:要么显示,要么不显示,不频繁切换的场景
v-show
语法:v-show=“表达式”,表达式值为 true,显示;false,隐藏
原理:基于CSS样式display来控制显示与隐藏
场景:频繁切换显示隐藏的场景
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         手链价格为:  <span v-if="customer.level>=0 && customer.level<=1">9.9</span>  
  11.                     <span v-else-if="customer.level>=2 && customer.level<=4">19.9</span>
  12.                     <span v-else>29.9</span>
  13.         <br/>
  14.         手链价格为:  <span v-show="customer.level>=0 && customer.level<=1">9.9</span>  
  15.                     <span v-show="customer.level>=2 && customer.level<=4">19.9</span>
  16.                     <span v-show="customer.level>=5">29.9</span>
  17.     </div>
  18.     <script type="module">
  19.         //导入vue模块
  20.         import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  21.         //创建vue应用实例
  22.         createApp({
  23.             data() {
  24.                 return {
  25.                     customer:{
  26.                         name:'张三',
  27.                         level:2
  28.                     }
  29.                 }
  30.             }
  31.         }).mount("#app")//控制html元素
  32.     </script>
  33. </body>
  34. </html>
复制代码
v-on

作用:为html标签绑定事件
语法:
v-on:事件名=“函数名”
简写为 @事件名=“函数名”
createApp({ data(){需要用到的数据}, methods:{需要用到的方法} })
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         <button v-on:click="money">点我有惊喜</button> &nbsp;
  11.         <button @click="love">再点更惊喜</button>
  12.     </div>
  13.     <script type="module">
  14.         //导入vue模块
  15.         import { createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  16.         //创建vue应用实例
  17.         createApp({
  18.             data() {
  19.                 return {
  20.                     //定义数据
  21.                 }
  22.             },
  23.             methods:{
  24.                 money: function(){
  25.                     alert('送你钱100')
  26.                 },
  27.                 love: function(){
  28.                     alert('爱你一万年')
  29.                 }
  30.             }
  31.         }).mount("#app");//控制html元素
  32.     </script>
  33. </body>
  34. </html>
复制代码
v-model

作用:在表单位素上使用,双向数据绑定。可以方便的 获取 或 设置 表单项数据
语法:v-model=“变量名”


注意:v-model 中绑定的变量,必须在data中定义。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         文章分类: <input type="text" v-model="searchConditions.category"/> <span>{{searchConditions.category}}</span>
  11.         发布状态: <input type="text" v-model="searchConditions.state"/> <span>{{searchConditions.state}}</span>
  12.         <button>搜索</button>
  13.         <button v-on:click="clear">重置</button>
  14.         <br />
  15.         <br />
  16.         <table border="1 solid" colspa="0" cellspacing="0">
  17.             <tr>
  18.                 <th>文章标题</th>
  19.                 <th>分类</th>
  20.                 <th>发表时间</th>
  21.                 <th>状态</th>
  22.                 <th>操作</th>
  23.             </tr>
  24.             <tr v-for="(article,index) in articleList">
  25.                 <td>{{article.title}}</td>
  26.                 <td>{{article.category}}</td>
  27.                 <td>{{article.time}}</td>
  28.                 <td>{{article.state}}</td>
  29.                 <td>
  30.                     <button>编辑</button>
  31.                     <button>删除</button>
  32.                 </td>
  33.             </tr>
  34.         </table>
  35.     </div>
  36.     <script type="module">
  37.         //导入vue模块
  38.         import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  39.         //创建vue应用实例
  40.         createApp({
  41.             data() {
  42.                 return {
  43.                     //定义数据
  44.                     searchConditions:{
  45.                         category:'',
  46.                         state:''
  47.                     },
  48.                     articleList: [{
  49.                         title: "医疗反腐绝非砍医护收入",
  50.                         category: "时事",
  51.                         time: "2023-09-5",
  52.                         state: "已发布"
  53.                     },
  54.                     {
  55.                         title: "中国男篮缘何一败涂地?",
  56.                         category: "篮球",
  57.                         time: "2023-09-5",
  58.                         state: "草稿"
  59.                     },
  60.                     {
  61.                         title: "华山景区已受大风影响阵风达7-8级,未来24小时将持续",
  62.                         category: "旅游",
  63.                         time: "2023-09-5",
  64.                         state: "已发布"
  65.                     }]
  66.                 }
  67.             }
  68.             ,
  69.             methods:{
  70.                 clear:function(){
  71.                     //清空category以及state的数据
  72.                     //在methods对应的方法里面,使用this就代表的是vue实例,可以使用this获取到vue实例中准备的数据
  73.                     this.searchConditions.category='';
  74.                     this.searchConditions.state='';
  75.                 }
  76.             }
  77.             ,
  78.             mounted:function(){
  79.                 console.log('Vue挂载完毕,发送请求获取数据')
  80.             }
  81.         }).mount("#app")//控制html元素
  82.     </script>
  83. </body>
  84. </html>
复制代码
3、生命周期

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



Axios

介绍:Axios 对原生的Ajax举行了封装,简化誊写,快速开辟。
官网:https://www.axios-http.cn/
Axios使用步骤
引入Axios的js文件(参照官网)
使用Axios发送哀求,并获取相应效果

Axios-哀求方式别名
为了方便起见,Axios已经为所有支持的哀求方法提供了别名
格式:axios.哀求方式(url [, data [, config]])

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <!-- 引入axios的js文件 -->
  10.     <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  11.     <script>
  12.         /* 发送请求 */
  13.         /* axios({
  14.             method:'get',
  15.             url:'http://localhost:8080/article/getAll'
  16.         }).then(result=>{
  17.             //成功的回调
  18.             //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
  19.             console.log(result.data);
  20.         }).catch(err=>{
  21.             //失败的回调
  22.             console.log(err);
  23.         }); */
  24.         let article = {
  25.             title: '明天会更好',
  26.             category: '生活',
  27.             time: '2000-01-01',
  28.             state: '草稿'
  29.         }
  30.         /*  axios({
  31.              method:'post',
  32.              url:'http://localhost:8080/article/add',
  33.              data:article
  34.          }).then(result=>{
  35.              //成功的回调
  36.              //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
  37.              console.log(result.data);
  38.          }).catch(err=>{
  39.              //失败的回调
  40.              console.log(err);
  41.          }); */
  42.         //别名的方式发送请求
  43.         /* axios.get('http://localhost:8080/article/getAll').then(result => {
  44.             //成功的回调
  45.             //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
  46.             console.log(result.data);
  47.         }).catch(err => {
  48.             //失败的回调
  49.             console.log(err);
  50.         }); */
  51.         axios.post('http://localhost:8080/article/add', article).then(result => {
  52.             //成功的回调
  53.             //result代表服务器响应的所有的数据,包含了响应头,响应体. result.data 代表的是接口响应的核心数据
  54.             console.log(result.data);
  55.         }).catch(err => {
  56.             //失败的回调
  57.             console.log(err);
  58.         });
  59.     </script>
  60. </body>
  61. </html>
复制代码
案例

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

钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜刮按钮绑定单击事件
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="app">
  10.         文章分类: <input type="text" v-model="searchConditions.category">
  11.         发布状态: <input type="text"  v-model="searchConditions.state">
  12.         <button v-on:click="search">搜索</button>
  13.         <br />
  14.         <br />
  15.         <table border="1 solid" colspa="0" cellspacing="0">
  16.             <tr>
  17.                 <th>文章标题</th>
  18.                 <th>分类</th>
  19.                 <th>发表时间</th>
  20.                 <th>状态</th>
  21.                 <th>操作</th>
  22.             </tr>
  23.             <tr v-for="(article,index) in articleList">
  24.                 <td>{{article.title}}</td>
  25.                 <td>{{article.category}}</td>
  26.                 <td>{{article.time}}</td>
  27.                 <td>{{article.state}}</td>
  28.                 <td>
  29.                     <button>编辑</button>
  30.                     <button>删除</button>
  31.                 </td>
  32.             </tr>
  33.             <!-- <tr>
  34.                 <td>标题2</td>
  35.                 <td>分类2</td>
  36.                 <td>2000-01-01</td>
  37.                 <td>已发布</td>
  38.                 <td>
  39.                     <button>编辑</button>
  40.                     <button>删除</button>
  41.                 </td>
  42.             </tr>
  43.             <tr>
  44.                 <td>标题3</td>
  45.                 <td>分类3</td>
  46.                 <td>2000-01-01</td>
  47.                 <td>已发布</td>
  48.                 <td>
  49.                     <button>编辑</button>
  50.                     <button>删除</button>
  51.                 </td>
  52.             </tr> -->
  53.         </table>
  54.     </div>
  55.     <!-- 导入axios的js文件 -->
  56.     <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  57.     <script type="module">
  58.         //导入vue模块
  59.         import {createApp} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
  60.         //创建vue应用实例
  61.         createApp({
  62.             data(){
  63.                 return {
  64.                     articleList:[],
  65.                     searchConditions:{
  66.                         category:'',
  67.                         state:''
  68.                     }
  69.                 }
  70.             },
  71.             methods:{
  72.                 //声明方法
  73.                 search:function(){
  74.                     //发送请求,完成搜索,携带搜索条件
  75.                     axios.get('http://localhost:8080/article/search?category='+this.searchConditions.category+'&state='+this.searchConditions.state)
  76.                     .then(result=>{
  77.                         //成功回调 result.data
  78.                         //把得到的数据赋值给articleList
  79.                         this.articleList=result.data
  80.                     }).catch(err=>{
  81.                         console.log(err);
  82.                     });
  83.                 }
  84.             },
  85.             //钩子函数mounted中,获取所有文章数据
  86.             mounted:function(){
  87.                 //发送异步请求  axios
  88.                 axios.get('http://localhost:8080/article/getAll').then(result=>{
  89.                     //成功回调
  90.                     //console.log(result.data);
  91.                     this.articleList=result.data;
  92.                 }).catch(err=>{
  93.                     //失败回调
  94.                     console.log(err);
  95.                 });
  96.             }
  97.         }).mount('#app');//控制html元素
  98.     </script>
  99. </body>
  100. </html>
复制代码
2、整站使用Vue(工程化)

1、情况准备




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



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


  • 配置npm的全局安装路径

使用管理员身份运行命令行,在命令行中,实行如下指令:
  1. 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) 。
  1. <!-- <script>
  2.   //写数据
  3.   export default{
  4.     data(){
  5.       return {
  6.         msg:'上海'
  7.       }
  8.     }
  9.   }
  10. </script> -->
  11. <script setup>
  12.   import {ref} from 'vue';
  13.   //调用ref函数,定义响应式数据
  14.   const msg = ref('西安');
  15.   //导入 Api.vue文件
  16.   import ApiVue from './Api.vue'
  17.   //导入Article.vue文件
  18.   import ArticleVue from './Article.vue'
  19. </script>
  20. <template>
  21.   <!-- html -->
  22.   <!-- <h1>北京</h1> -->
  23.   <!-- <h1>{{ msg }}</h1>
  24.   <br>
  25.   <ApiVue/> -->
  26.   <ArticleVue/>
  27. </template>
  28. <style scoped>
  29.   /* 样式 */
  30.   h1{
  31.     color: red;
  32.   }
  33. </style>
复制代码
4、API风格

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

setup:是一个标识,告诉Vue需要举行一些处理,让我们可以更简便的使用组合式API。
ref():接收一个内部值,返回一个相应式的ref对象,此对象只有一个指向内部值的属性 value。
onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后实行。
  1. <script setup>
  2.     import {ref,onMounted} from 'vue'
  3.     //声明响应式数据 ref  响应式对象有一个内部的属性value
  4.     const count = ref(0); //在组合式api中,一般需要把数据定义为响应式数据
  5.     //const count=0;
  6.     //声明函数
  7.     function increment(){
  8.         count.value++;
  9.     }
  10.     //声明钩子函数 onMounted
  11.     onMounted(()=>{
  12.         console.log('vue 已经挂载完毕了...');
  13.     });
  14. </script>
  15. <template>
  16.     <!-- 写html元素 -->
  17.     <button @click="increment">count: {{ count }}</button>
  18. </template>
复制代码

选项式API,可以用包罗多个选项的对象来形貌组件的逻辑,如:data,methods,mounted等。
5、案例

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

钩子函数mounted中, 获取所有的文章数据
使用v-for指令,把数据渲染到表格上展示
使用v-model指令完成表单数据的双向绑定
使用v-on指令为搜刮按钮绑定单击事件



在哀求或相应被 then 或 catch 处理前拦截它们

Artcle.vue
  1. <script setup>
  2.     import {articleGetAllService,articleSearchService} from '@/api/article.js';
  3.     import {ref} from 'vue';
  4.    
  5.     //定义响应式数据  ref
  6.     const articleList = ref([]);
  7.     //获取所有文章数据
  8.     //同步获取articleGetAllService的返回结果  async await
  9.     const getAllArticle=async function(){
  10.         let data = await articleGetAllService();
  11.         articleList.value=data;
  12.     }
  13.     getAllArticle();
  14.    
  15.    
  16.     //定义响应式数据 searchConditions
  17.     const searchConditions = ref({
  18.         category:'',
  19.         state:''
  20.     })
  21.     //声明search函数
  22.     const search = async function(){
  23.         //文章搜索
  24.         let data = await articleSearchService({...searchConditions.value});
  25.         articleList.value = data;
  26.     }
  27. </script>
  28. <template>
  29.     <!-- html元素 -->
  30.     <div>
  31.         文章分类: <input type="text" v-model="searchConditions.category">
  32.         发布状态: <input type="text" v-model="searchConditions.state">
  33.         <button v-on:click="search">搜索</button>
  34.         <br />
  35.         <br />
  36.         <table border="1 solid" colspa="0" cellspacing="0">
  37.             <tr>
  38.                 <th>文章标题</th>
  39.                 <th>分类</th>
  40.                 <th>发表时间</th>
  41.                 <th>状态</th>
  42.                 <th>操作</th>
  43.             </tr>
  44.             <tr v-for="(article,index) in articleList">
  45.                 <td>{{article.title}}</td>
  46.                 <td>{{article.category}}</td>
  47.                 <td>{{article.time}}</td>
  48.                 <td>{{article.state}}</td>
  49.                 <td>
  50.                     <button>编辑</button>
  51.                     <button>删除</button>
  52.                 </td>
  53.             </tr>
  54.         </table>
  55.     </div>
  56. </template>
复制代码
artcle.js
  1. /* //导入axios  npm install axios
  2. import axios from 'axios';
  3. //定义一个变量,记录公共的前缀  ,  baseURL
  4. const baseURL = 'http://localhost:8080';
  5. const instance = axios.create({baseURL}) */
  6. import request from '@/util/request.js'
  7. export function articleGetAllService() {
  8.     return request.get('/article/getAll');
  9. }
  10. export function articleSearchService(conditions) {
  11.     return request.get('/article/search', { params: conditions });
  12. }
复制代码
request.js
  1. //定制请求的实例
  2. //导入axios  npm install axios
  3. import axios from 'axios';
  4. //定义一个变量,记录公共的前缀  ,  baseURL
  5. const baseURL = 'http://localhost:8080';
  6. const instance = axios.create({baseURL})
  7. //添加响应拦截器
  8. instance.interceptors.response.use(
  9.     result=>{
  10.         return result.data;
  11.     },
  12.     err=>{
  13.         alert('服务异常');
  14.         return Promise.reject(err);//异步的状态转化成失败的状态
  15.     }
  16. )
  17. export default instance;
复制代码
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组件库(参照官方文档)
  1. import { createApp } from 'vue'//导入vue
  2. import ElementPlus from 'element-plus'//导入element-plus
  3. import 'element-plus/dist/index.css'//导入element-plus的样式
  4. import App from './App.vue'//导入app.vue
  5. import locale from 'element-plus/dist/locale/zh-cn.js'
  6. const app = createApp(App)//创建应用实例
  7. app.use(ElementPlus,{locale})//使用element-plus
  8. app.mount('#app')//控制html元素
复制代码
制作组件:
访问Element官方文档,复制组件代码,调解
Button.vue
  1. <script lang="ts" setup>
  2. import {
  3.     Check,
  4.     Delete,
  5.     Edit,
  6.     Message,
  7.     Search,
  8.     Star,
  9. } from '@element-plus/icons-vue'
  10. </script>
  11. <template>
  12.     <el-row class="mb-4">
  13.         <el-button>Default</el-button>
  14.         <el-button type="primary" disabled="true">编辑</el-button>
  15.         <el-button type="success" loading="true">查看</el-button>
  16.         
  17.     </el-row>
  18.     <el-row class="mb-4">
  19.         
  20.         <el-button type="info" plain>Info</el-button>
  21.         <el-button type="warning" plain>Warning</el-button>
  22.         <el-button type="danger" plain>Danger</el-button>
  23.     </el-row>
  24. </template>
复制代码
App.vue
  1. <script setup>
  2.   import ButtonVue from './Button.vue'
  3.   import ArticleVue from './Article.vue'
  4. </script>
  5. <template>
  6.   <!-- <ButtonVue/> -->
  7.   <ArticleVue/>
  8. </template>
复制代码
2、常用组件


Article.vue
  1. <script lang="ts" setup>
  2. import { reactive } from 'vue'
  3. const formInline = reactive({
  4.     user: '',
  5.     region: '',
  6.     date: '',
  7. })
  8. const onSubmit = () => {
  9.     console.log('submit!')
  10. }
  11. import { ref } from 'vue'
  12. const currentPage4 = ref(2)
  13. const pageSize4 = ref(5)
  14. const small = ref(false)
  15. const background = ref(false)
  16. const disabled = ref(false)
  17. const total = ref(20)
  18. const handleSizeChange = (val: number) => {
  19.     console.log(`${val} items per page`)
  20. }
  21. const handleCurrentChange = (val: number) => {
  22.     console.log(`current page: ${val}`)
  23. }
  24. import {
  25.     Delete,
  26.     Edit,
  27. } from '@element-plus/icons-vue'
  28. const tableData = [
  29.     {
  30.         title: '标题1',
  31.         category: '时事',
  32.         time: '2000-01-01',
  33.         state: '已发布',
  34.     },
  35.     {
  36.         title: '标题1',
  37.         category: '时事',
  38.         time: '2000-01-01',
  39.         state: '已发布',
  40.     },
  41.     {
  42.         title: '标题1',
  43.         category: '时事',
  44.         time: '2000-01-01',
  45.         state: '已发布',
  46.     },
  47.     {
  48.         title: '标题1',
  49.         category: '时事',
  50.         time: '2000-01-01',
  51.         state: '已发布',
  52.     },
  53.     {
  54.         title: '标题1',
  55.         category: '时事',
  56.         time: '2000-01-01',
  57.         state: '已发布',
  58.     }
  59. ]
  60. </script>
  61. <template>
  62.     <el-card class="box-card">
  63.         <div class="card-header">
  64.             <span>文章管理</span>
  65.             <el-button type="primary">发布文章</el-button>
  66.         </div>
  67.         <div style="margin-top: 20px;">
  68.             <hr>
  69.         </div>
  70.         <el-form :inline="true" :model="formInline" class="demo-form-inline">
  71.             <el-form-item label="文章分类:">
  72.                 <el-select v-model="formInline.region" placeholder="请选择" clearable>
  73.                     <el-option label="时事" value="时事" />
  74.                     <el-option label="篮球" value="篮球" />
  75.                 </el-select>
  76.             </el-form-item>
  77.             <el-form-item label="发布状态:">
  78.                 <el-select v-model="formInline.region" placeholder="请选择" clearable>
  79.                     <el-option label="已发布" value="已发布" />
  80.                     <el-option label="草稿" value="草稿" />
  81.                 </el-select>
  82.             </el-form-item>
  83.             <el-form-item>
  84.                 <el-button type="primary" @click="onSubmit">搜索</el-button>
  85.             </el-form-item>
  86.             <el-form-item>
  87.                 <el-button type="default" @click="onSubmit">重置</el-button>
  88.             </el-form-item>
  89.         </el-form>
  90.         <el-table :data="tableData" style="width: 100%">
  91.             <el-table-column prop="title" label="文章标题" />
  92.             <el-table-column prop="category" label="分类" />
  93.             <el-table-column prop="time" label="发表时间" />
  94.             <el-table-column prop="state" label="状态" />
  95.             <el-table-column label="操作" width="180">
  96.                 <el-row>
  97.                     <el-button type="primary" :icon="Edit" circle />
  98.                     <el-button type="danger" :icon="Delete" circle />
  99.                 </el-row>
  100.             </el-table-column>
  101.         </el-table>
  102.         <el-pagination class="el-p" v-model:current-page="currentPage4" v-model:page-size="pageSize4"
  103.             :page-sizes="[5, 10, 15, 20]" :small="small" :disabled="disabled" :background="background"
  104.             layout="jumper,total, sizes, prev, pager, next" :total="total" @size-change="handleSizeChange"
  105.             @current-change="handleCurrentChange" />
  106.     </el-card>
  107. </template>
  108. <style scoped>
  109. .el-p {
  110.     margin-top: 20px;
  111.     display: flex;
  112.     justify-content: flex-end;
  113. }
  114. .card-header {
  115.     display: flex;
  116.     justify-content: space-between;
  117. }
  118. </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
  1. import './assets/main.scss'
  2. import { createApp } from 'vue'
  3. import ElementPlus from 'element-plus'
  4. import 'element-plus/dist/index.css'
  5. import router from '@/router'
  6. import App from './App.vue'
  7. import {createPinia} from 'pinia'
  8. import { createPersistedState } from 'pinia-persistedstate-plugin'
  9. import locale from 'element-plus/dist/locale/zh-cn.js'
  10. const app = createApp(App);
  11. const pinia = createPinia();
  12. const persist = createPersistedState();
  13. pinia.use(persist)
  14. app.use(pinia)
  15. app.use(router)
  16. app.use(ElementPlus,{locale});
  17. app.mount('#app')
复制代码
2. 功能开辟

注册登录


搭建页面
数据绑定:参考接口文档给属性起名
表单校验:
el-form标签上通过rules属性,绑定校验规则
el-form-item标签上通过prop属性,指定校验项
Login.vue
  1. <script setup>
  2. import { User, Lock } from '@element-plus/icons-vue'
  3. import { ref } from 'vue'
  4. import { ElMessage } from 'element-plus'
  5. //控制注册与登录表单的显示, 默认显示注册
  6. const isRegister = ref(false)
  7. //定义数据模型
  8. const registerData = ref({
  9.     username: '',
  10.     password: '',
  11.     rePassword: ''
  12. })
  13. //校验密码的函数
  14. const checkRePassword = (rule, value, callback) => {
  15.     if (value === '') {
  16.         callback(new Error('请再次确认密码'))
  17.     } else if (value !== registerData.value.password) {
  18.         callback(new Error('请确保两次输入的密码一样'))
  19.     } else {
  20.         callback()
  21.     }
  22. }
  23. //定义表单校验规则
  24. const rules = {
  25.     username: [
  26.         { required: true, message: '请输入用户名', trigger: 'blur' },
  27.         { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }
  28.     ],
  29.     password: [
  30.         { required: true, message: '请输入密码', trigger: 'blur' },
  31.         { min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }
  32.     ],
  33.     rePassword: [
  34.         { validator: checkRePassword, trigger: 'blur' }
  35.     ]
  36. }
  37. //调用后台接口,完成注册
  38. import { userRegisterService, userLoginService} from '@/api/user.js'
  39. const register = async () => {
  40.     //registerData是一个响应式对象,如果要获取值,需要.value
  41.     let result = await userRegisterService(registerData.value);
  42.     /* if (result.code === 0) {
  43.         //成功了
  44.         alert(result.msg ? result.msg : '注册成功');
  45.     }else{
  46.         //失败了
  47.         alert('注册失败')
  48.     } */
  49.     //alert(result.msg ? result.msg : '注册成功');
  50.     ElMessage.success(result.msg ? result.msg : '注册成功')
  51. }
  52. //绑定数据,复用注册表单的数据模型
  53. //表单数据校验
  54. //登录函数
  55. import {useTokenStore} from '@/stores/token.js'
  56. import {useRouter} from 'vue-router'
  57. const router = useRouter()
  58. const tokenStore = useTokenStore();
  59. const login =async ()=>{
  60.     //调用接口,完成登录
  61.    let result =  await userLoginService(registerData.value);
  62.    /* if(result.code===0){
  63.     alert(result.msg? result.msg : '登录成功')
  64.    }else{
  65.     alert('登录失败')
  66.    } */
  67.    //alert(result.msg? result.msg : '登录成功')
  68.    ElMessage.success(result.msg ? result.msg : '登录成功')
  69.    //把得到的token存储到pinia中
  70.    tokenStore.setToken(result.data)
  71.    //跳转到首页 路由完成跳转
  72.    router.push('/')
  73. }
  74. //定义函数,清空数据模型的数据
  75. const clearRegisterData = ()=>{
  76.     registerData.value={
  77.         username:'',
  78.         password:'',
  79.         rePassword:''
  80.     }
  81. }
  82. </script>
  83. <template>
  84.     <el-row class="login-page">
  85.         <el-col :span="12" class="bg"></el-col>
  86.         <el-col :span="6" :offset="3" class="form">
  87.             <!-- 注册表单 -->
  88.             <el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">
  89.                 <el-form-item>
  90.                     <h1>注册</h1>
  91.                 </el-form-item>
  92.                 <el-form-item prop="username">
  93.                     <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
  94.                 </el-form-item>
  95.                 <el-form-item prop="password">
  96.                     <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"
  97.                         v-model="registerData.password"></el-input>
  98.                 </el-form-item>
  99.                 <el-form-item prop="rePassword">
  100.                     <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"
  101.                         v-model="registerData.rePassword"></el-input>
  102.                 </el-form-item>
  103.                 <!-- 注册按钮 -->
  104.                 <el-form-item>
  105.                     <el-button class="button" type="primary" auto-insert-space @click="register">
  106.                         注册
  107.                     </el-button>
  108.                 </el-form-item>
  109.                 <el-form-item class="flex">
  110.                     <el-link type="info" :underline="false" @click="isRegister = false;clearRegisterData()">
  111.                         ← 返回
  112.                     </el-link>
  113.                 </el-form-item>
  114.             </el-form>
  115.             <!-- 登录表单 -->
  116.             <el-form ref="form" size="large" autocomplete="off" v-else :model="registerData" :rules="rules">
  117.                 <el-form-item>
  118.                     <h1>登录</h1>
  119.                 </el-form-item>
  120.                 <el-form-item prop="username">
  121.                     <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
  122.                 </el-form-item>
  123.                 <el-form-item prop="password">
  124.                     <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
  125.                 </el-form-item>
  126.                 <el-form-item class="flex">
  127.                     <div class="flex">
  128.                         <el-checkbox>记住我</el-checkbox>
  129.                         <el-link type="primary" :underline="false">忘记密码?</el-link>
  130.                     </div>
  131.                 </el-form-item>
  132.                 <!-- 登录按钮 -->
  133.                 <el-form-item>
  134.                     <el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
  135.                 </el-form-item>
  136.                 <el-form-item class="flex">
  137.                     <el-link type="info" :underline="false" @click="isRegister = true;clearRegisterData()">
  138.                         注册 →
  139.                     </el-link>
  140.                 </el-form-item>
  141.             </el-form>
  142.         </el-col>
  143.     </el-row>
  144. </template>
  145. <style lang="scss" scoped>
  146. /* 样式 */
  147. .login-page {
  148.     height: 100vh;
  149.     background-color: #fff;
  150.     .bg {
  151.         background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
  152.             url('@/assets/login_bg.jpg') no-repeat center / cover;
  153.         border-radius: 0 20px 20px 0;
  154.     }
  155.     .form {
  156.         display: flex;
  157.         flex-direction: column;
  158.         justify-content: center;
  159.         user-select: none;
  160.         .title {
  161.             margin: 0 auto;
  162.         }
  163.         .button {
  164.             width: 100%;
  165.         }
  166.         .flex {
  167.             width: 100%;
  168.             display: flex;
  169.             justify-content: space-between;
  170.         }
  171.     }
  172. }
  173. </style>
复制代码
api/user.js
  1. //导入request.js请求工具
  2. import request from '@/utils/request.js'
  3. //提供调用注册接口的函数
  4. export const userRegisterService = (registerData)=>{
  5.     //借助于UrlSearchParams完成传递
  6.     const params = new URLSearchParams()
  7.     for(let key in registerData){
  8.         params.append(key,registerData[key]);
  9.     }
  10.     return request.post('/user/register',params);
  11. }
  12. //提供调用登录接口的函数
  13. export const userLoginService = (loginData)=>{
  14.     const params = new URLSearchParams();
  15.     for(let key in loginData){
  16.         params.append(key,loginData[key])
  17.     }
  18.     return request.post('/user/login',params)
  19. }
  20. //获取用户详细信息
  21. export const userInfoService = ()=>{
  22.     return request.get('/user/userInfo')
  23. }
  24. //修改个人信息
  25. export const userInfoUpdateService = (userInfoData)=>{
  26.    return request.put('/user/update',userInfoData)
  27. }
  28. //修改头像
  29. export const userAvatarUpdateService = (avatarUrl)=>{
  30.     const params = new URLSearchParams();
  31.     params.append('avatarUrl',avatarUrl)
  32.     return request.patch('/user/updateAvatar',params)
  33. }
复制代码
跨域
由于浏览器的同源计谋限制,向不同源(不同协议、不同域名、不同端口)发送ajax哀求会失败

优化axios相应拦截器



request.js
  1. //定制请求的实例
  2. //导入axios  npm install axios
  3. import axios from 'axios';
  4. import { ElMessage } from 'element-plus'
  5. //定义一个变量,记录公共的前缀  ,  baseURL
  6. //const baseURL = 'http://localhost:8080';
  7. const baseURL = '/api';
  8. const instance = axios.create({ baseURL })
  9. import {useTokenStore} from '@/stores/token.js'
  10. //添加请求拦截器
  11. instance.interceptors.request.use(
  12.     (config)=>{
  13.         //请求前的回调
  14.         //添加token
  15.         const tokenStore = useTokenStore();
  16.         //判断有没有token
  17.         if(tokenStore.token){
  18.             config.headers.Authorization = tokenStore.token
  19.         }
  20.         return config;
  21.     },
  22.     (err)=>{
  23.         //请求错误的回调
  24.         Promise.reject(err)
  25.     }
  26. )
  27. /* import {useRouter} from 'vue-router'
  28. const router = useRouter(); */
  29. import router from '@/router'
  30. //添加响应拦截器
  31. instance.interceptors.response.use(
  32.     result => {
  33.         //判断业务状态码
  34.         if(result.data.code===0){
  35.             return result.data;
  36.         }
  37.         //操作失败
  38.         //alert(result.data.msg?result.data.msg:'服务异常')
  39.         ElMessage.error(result.data.msg?result.data.msg:'服务异常')
  40.         //异步操作的状态转换为失败
  41.         return Promise.reject(result.data)
  42.         
  43.     },
  44.     err => {
  45.         //判断响应状态码,如果为401,则证明未登录,提示请登录,并跳转到登录页面
  46.         if(err.response.status===401){
  47.             ElMessage.error('请先登录')
  48.             router.push('/login')
  49.         }else{
  50.             ElMessage.error('服务异常')
  51.         }
  52.       
  53.         return Promise.reject(err);//异步的状态转化成失败的状态
  54.     }
  55. )
  56. export default instance;
复制代码
vite.config.js
  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. import path from 'node:path'
  5. // https://vitejs.dev/config/
  6. export default defineConfig({
  7.   plugins: [
  8.     vue(),
  9.   ],
  10.   resolve: {
  11.     alias: {
  12.       '@': fileURLToPath(new URL('./src', import.meta.url))
  13.     }
  14.   }
  15.   ,
  16.   server:{
  17.     proxy:{
  18.       '/api':{//获取路径中包含了/api的请求
  19.           target:'http://localhost:8080',//后台服务所在的源
  20.           changeOrigin:true,//修改源
  21.           rewrite:(path)=>path.replace(/^\/api/,'')///api替换为''
  22.       }
  23.     }
  24.   }
  25. })
复制代码
主页面布局


Layout.vue
  1. <script setup>
  2. import {
  3.     Management,
  4.     Promotion,
  5.     UserFilled,
  6.     User,
  7.     Crop,
  8.     EditPen,
  9.     SwitchButton,
  10.     CaretBottom
  11. } from '@element-plus/icons-vue'
  12. import avatar from '@/assets/default.png'
  13. import {userInfoService} from '@/api/user.js'
  14. import useUserInfoStore from '@/stores/userInfo.js'
  15. import {useTokenStore} from '@/stores/token.js'
  16. const tokenStore = useTokenStore();
  17. const userInfoStore = useUserInfoStore();
  18. //调用函数,获取用户详细信息
  19. const getUserInfo = async()=>{
  20.     //调用接口
  21.     let result = await userInfoService();
  22.     //数据存储到pinia中
  23.     userInfoStore.setInfo(result.data);
  24. }
  25. getUserInfo();
  26. //条目被点击后,调用的函数
  27. import {useRouter} from 'vue-router'
  28. const router = useRouter();
  29. import {ElMessage,ElMessageBox} from 'element-plus'
  30. const handleCommand = (command)=>{
  31.     //判断指令
  32.     if(command === 'logout'){
  33.         //退出登录
  34.         ElMessageBox.confirm(
  35.         '您确认要退出吗?',
  36.         '温馨提示',
  37.         {
  38.             confirmButtonText: '确认',
  39.             cancelButtonText: '取消',
  40.             type: 'warning',
  41.         }
  42.     )
  43.         .then(async () => {
  44.             //退出登录
  45.             //1.清空pinia中存储的token以及个人信息
  46.             tokenStore.removeToken()
  47.             userInfoStore.removeInfo()
  48.             //2.跳转到登录页面
  49.             router.push('/login')
  50.             ElMessage({
  51.                 type: 'success',
  52.                 message: '退出登录成功',
  53.             })
  54.             
  55.         })
  56.         .catch(() => {
  57.             ElMessage({
  58.                 type: 'info',
  59.                 message: '用户取消了退出登录',
  60.             })
  61.         })
  62.     }else{
  63.         //路由
  64.         router.push('/user/'+command)
  65.     }
  66. }
  67. </script>
  68. <template>
  69.     <!-- element-plus中的容器 -->
  70.     <el-container class="layout-container">
  71.         <!-- 左侧菜单 -->
  72.         <el-aside width="200px">
  73.             <div class="el-aside__logo"></div>
  74.             <!-- element-plus的菜单标签 -->
  75.             <el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"
  76.                 router>
  77.                 <el-menu-item index="/article/category">
  78.                     <el-icon>
  79.                         <Management />
  80.                     </el-icon>
  81.                     <span>文章分类</span>
  82.                 </el-menu-item>
  83.                 <el-menu-item index="/article/manage">
  84.                     <el-icon>
  85.                         <Promotion />
  86.                     </el-icon>
  87.                     <span>文章管理</span>
  88.                 </el-menu-item>
  89.                 <el-sub-menu >
  90.                     <template #title>
  91.                         <el-icon>
  92.                             <UserFilled />
  93.                         </el-icon>
  94.                         <span>个人中心</span>
  95.                     </template>
  96.                     <el-menu-item index="/user/info">
  97.                         <el-icon>
  98.                             <User />
  99.                         </el-icon>
  100.                         <span>基本资料</span>
  101.                     </el-menu-item>
  102.                     <el-menu-item index="/user/avatar">
  103.                         <el-icon>
  104.                             <Crop />
  105.                         </el-icon>
  106.                         <span>更换头像</span>
  107.                     </el-menu-item>
  108.                     <el-menu-item index="/user/resetPassword">
  109.                         <el-icon>
  110.                             <EditPen />
  111.                         </el-icon>
  112.                         <span>重置密码</span>
  113.                     </el-menu-item>
  114.                 </el-sub-menu>
  115.             </el-menu>
  116.         </el-aside>
  117.         <!-- 右侧主区域 -->
  118.         <el-container>
  119.             <!-- 头部区域 -->
  120.             <el-header>
  121.                 <div>黑马程序员:<strong>{{ userInfoStore.info.nickname }}</strong></div>
  122.                 <!-- 下拉菜单 -->
  123.                 <!-- command: 条目被点击后会触发,在事件函数上可以声明一个参数,接收条目对应的指令 -->
  124.                 <el-dropdown placement="bottom-end" @command="handleCommand">
  125.                     <span class="el-dropdown__box">
  126.                         <el-avatar :src="userInfoStore.info.userPic? userInfoStore.info.userPic:avatar" />
  127.                         <el-icon>
  128.                             <CaretBottom />
  129.                         </el-icon>
  130.                     </span>
  131.                     <template #dropdown>
  132.                         <el-dropdown-menu>
  133.                             <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
  134.                             <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
  135.                             <el-dropdown-item command="resetPassword" :icon="EditPen">重置密码</el-dropdown-item>
  136.                             <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
  137.                         </el-dropdown-menu>
  138.                     </template>
  139.                 </el-dropdown>
  140.             </el-header>
  141.             <!-- 中间区域 -->
  142.             <el-main>
  143.                 <!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">
  144.                     内容展示区
  145.                 </div> -->
  146.                 <router-view></router-view>
  147.             </el-main>
  148.             <!-- 底部区域 -->
  149.             <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
  150.         </el-container>
  151.     </el-container>
  152. </template>
  153. <style lang="scss" scoped>
  154. .layout-container {
  155.     height: 100vh;
  156.     .el-aside {
  157.         background-color: #232323;
  158.         &__logo {
  159.             height: 120px;
  160.             background: url('@/assets/logo.png') no-repeat center / 120px auto;
  161.         }
  162.         .el-menu {
  163.             border-right: none;
  164.         }
  165.     }
  166.     .el-header {
  167.         background-color: #fff;
  168.         display: flex;
  169.         align-items: center;
  170.         justify-content: space-between;
  171.         .el-dropdown__box {
  172.             display: flex;
  173.             align-items: center;
  174.             .el-icon {
  175.                 color: #999;
  176.                 margin-left: 10px;
  177.             }
  178.             &:active,
  179.             &:focus {
  180.                 outline: none;
  181.             }
  182.         }
  183.     }
  184.     .el-footer {
  185.         display: flex;
  186.         align-items: center;
  187.         justify-content: center;
  188.         font-size: 14px;
  189.         color: #666;
  190.     }
  191. }
  192. </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
  1. import { createRouter, createWebHistory } from 'vue-router'
  2. //导入组件
  3. import LoginVue from '@/views/Login.vue'
  4. import LayoutVue from '@/views/Layout.vue'
  5. import ArticleCategoryVue from '@/views/article/ArticleCategory.vue'
  6. import ArticleManageVue from '@/views/article/ArticleManage.vue'
  7. import UserAvatarVue from '@/views/user/UserAvatar.vue'
  8. import UserInfoVue from '@/views/user/UserInfo.vue'
  9. import UserResetPasswordVue from '@/views/user/UserResetPassword.vue'
  10. //定义路由关系
  11. const routes = [
  12.     { path: '/login', component: LoginVue },
  13.     {
  14.         path: '/', component: LayoutVue,redirect:'/article/manage', children: [
  15.             { path: '/article/category', component: ArticleCategoryVue },
  16.             { path: '/article/manage', component: ArticleManageVue },
  17.             { path: '/user/info', component: UserInfoVue },
  18.             { path: '/user/avatar', component: UserAvatarVue },
  19.             { path: '/user/resetPassword', component: UserResetPasswordVue }
  20.         ]
  21.     }
  22. ]
  23. //创建路由器
  24. const router = createRouter({
  25.     history: createWebHistory(),
  26.     routes: routes
  27. })
  28. //导出路由
  29. export default router
复制代码
文章分类


api/article.js
  1. import request from '@/utils/request.js'
  2. import { useTokenStore } from '@/stores/token.js'
  3. //文章分类列表查询
  4. export const articleCategoryListService = ()=>{
  5.     //const tokenStore = useTokenStore();
  6.     //在pinia中定义的响应式数据,都不需要.value
  7.     //return request.get('/category',{headers:{'Authorization':tokenStore.token}})
  8.     return request.get('/category')
  9. }
  10. //文章分类添加
  11. export const articleCategoryAddService = (categoryData)=>{
  12.     return request.post('/category',categoryData)
  13. }
  14. //文章分类修改
  15. export const articleCategoryUpdateService = (categoryData)=>{
  16.    return  request.put('/category',categoryData)
  17. }
  18. //文章分类删除
  19. export const articleCategoryDeleteService = (id)=>{
  20.     return request.delete('/category?id='+id)
  21. }
  22. //文章列表查询
  23. export const articleListService = (params)=>{
  24.    return  request.get('/article',{params:params})
  25. }
  26. //文章添加
  27. export const articleAddService = (articleData)=>{
  28.     return request.post('/article',articleData);
  29. }
复制代码
AticleCategory.vue
  1. <script setup>
  2. import {
  3.     Edit,
  4.     Delete
  5. } from '@element-plus/icons-vue'
  6. import { ref } from 'vue'
  7. const categorys = ref([
  8.     {
  9.         "id": 3,
  10.         "categoryName": "美食",
  11.         "categoryAlias": "my",
  12.         "createTime": "2023-09-02 12:06:59",
  13.         "updateTime": "2023-09-02 12:06:59"
  14.     },
  15.     {
  16.         "id": 4,
  17.         "categoryName": "娱乐",
  18.         "categoryAlias": "yl",
  19.         "createTime": "2023-09-02 12:08:16",
  20.         "updateTime": "2023-09-02 12:08:16"
  21.     },
  22.     {
  23.         "id": 5,
  24.         "categoryName": "军事",
  25.         "categoryAlias": "js",
  26.         "createTime": "2023-09-02 12:08:33",
  27.         "updateTime": "2023-09-02 12:08:33"
  28.     }
  29. ])
  30. //声明一个异步的函数
  31. import { articleCategoryListService, articleCategoryAddService, articleCategoryUpdateService,articleCategoryDeleteService } from '@/api/article.js'
  32. const articleCategoryList = async () => {
  33.     let result = await articleCategoryListService();
  34.     categorys.value = result.data;
  35. }
  36. articleCategoryList();
  37. //控制添加分类弹窗
  38. const dialogVisible = ref(false)
  39. //添加分类数据模型
  40. const categoryModel = ref({
  41.     categoryName: '',
  42.     categoryAlias: ''
  43. })
  44. //添加分类表单校验
  45. const rules = {
  46.     categoryName: [
  47.         { required: true, message: '请输入分类名称', trigger: 'blur' },
  48.     ],
  49.     categoryAlias: [
  50.         { required: true, message: '请输入分类别名', trigger: 'blur' },
  51.     ]
  52. }
  53. //调用接口,添加表单
  54. import { ElMessage } from 'element-plus'
  55. const addCategory = async () => {
  56.     //调用接口
  57.     let result = await articleCategoryAddService(categoryModel.value);
  58.     ElMessage.success(result.msg ? result.msg : '添加成功')
  59.     //调用获取所有文章分类的函数
  60.     articleCategoryList();
  61.     dialogVisible.value = false;
  62. }
  63. //定义变量,控制标题的展示
  64. const title = ref('')
  65. //展示编辑弹窗
  66. const showDialog = (row) => {
  67.     dialogVisible.value = true; title.value = '编辑分类'
  68.     //数据拷贝
  69.     categoryModel.value.categoryName = row.categoryName;
  70.     categoryModel.value.categoryAlias = row.categoryAlias;
  71.     //扩展id属性,将来需要传递给后台,完成分类的修改
  72.     categoryModel.value.id = row.id
  73. }
  74. //编辑分类
  75. const updateCategory = async () => {
  76.     //调用接口
  77.     let result = await articleCategoryUpdateService(categoryModel.value);
  78.     ElMessage.success(result.msg ? result.msg : '修改成功')
  79.     //调用获取所有分类的函数
  80.     articleCategoryList();
  81.     //隐藏弹窗
  82.     dialogVisible.value = false;
  83. }
  84. //清空模型的数据
  85. const clearData = () => {
  86.     categoryModel.value.categoryName = '';
  87.     categoryModel.value.categoryAlias = '';
  88. }
  89. //删除分类
  90. import {ElMessageBox} from 'element-plus'
  91. const deleteCategory = (row) => {
  92.     //提示用户  确认框
  93.     ElMessageBox.confirm(
  94.         '你确认要删除该分类信息吗?',
  95.         '温馨提示',
  96.         {
  97.             confirmButtonText: '确认',
  98.             cancelButtonText: '取消',
  99.             type: 'warning',
  100.         }
  101.     )
  102.         .then(async () => {
  103.             //调用接口
  104.             let result = await articleCategoryDeleteService(row.id);
  105.             ElMessage({
  106.                 type: 'success',
  107.                 message: '删除成功',
  108.             })
  109.             //刷新列表
  110.             articleCategoryList();
  111.         })
  112.         .catch(() => {
  113.             ElMessage({
  114.                 type: 'info',
  115.                 message: '用户取消了删除',
  116.             })
  117.         })
  118. }
  119. </script>
  120. <template>
  121.     <el-card class="page-container">
  122.         <template #header>
  123.             <div class="header">
  124.                 <span>文章分类</span>
  125.                 <div class="extra">
  126.                     <el-button type="primary" @click="dialogVisible = true; title = '添加分类'; clearData()">添加分类</el-button>
  127.                 </div>
  128.             </div>
  129.         </template>
  130.         <el-table :data="categorys" style="width: 100%">
  131.             <el-table-column label="序号" width="100" type="index"> </el-table-column>
  132.             <el-table-column label="分类名称" prop="categoryName"></el-table-column>
  133.             <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
  134.             <el-table-column label="操作" width="100">
  135.                 <template #default="{ row }">
  136.                     <el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>
  137.                     <el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button>
  138.                 </template>
  139.             </el-table-column>
  140.             <template #empty>
  141.                 <el-empty description="没有数据" />
  142.             </template>
  143.         </el-table>
  144.         <!-- 添加分类弹窗 -->
  145.         <el-dialog v-model="dialogVisible" :title="title" width="30%">
  146.             <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
  147.                 <el-form-item label="分类名称" prop="categoryName">
  148.                     <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
  149.                 </el-form-item>
  150.                 <el-form-item label="分类别名" prop="categoryAlias">
  151.                     <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
  152.                 </el-form-item>
  153.             </el-form>
  154.             <template #footer>
  155.                 <span class="dialog-footer">
  156.                     <el-button @click="dialogVisible = false">取消</el-button>
  157.                     <el-button type="primary" @click="title == '添加分类' ? addCategory() : updateCategory()"> 确认 </el-button>
  158.                 </span>
  159.             </template>
  160.         </el-dialog>
  161.     </el-card>
  162. </template>
  163. <style lang="scss" scoped>
  164. .page-container {
  165.     min-height: 100%;
  166.     box-sizing: border-box;
  167.     .header {
  168.         display: flex;
  169.         align-items: center;
  170.         justify-content: space-between;
  171.     }
  172. }
  173. </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


未登录同一处理

文章管理

添加文章分类

添加分类弹窗页面
  1. <!-- 添加分类弹窗 -->
  2. <el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
  3.     <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
  4.         <el-form-item label="分类名称" prop="categoryName">
  5.             <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
  6.         </el-form-item>
  7.         <el-form-item label="分类别名" prop="categoryAlias">
  8.             <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
  9.         </el-form-item>
  10.     </el-form>
  11.     <template #footer>
  12.         <span class="dialog-footer">
  13.             <el-button @click="dialogVisible = false">取消</el-button>
  14.             <el-button type="primary"> 确认 </el-button>
  15.         </span>
  16.     </template>
  17. </el-dialog>
复制代码
数据模子和校验规则
  1.   //控制添加分类弹窗
  2.     const dialogVisible = ref(false)
  3.    
  4.     //添加分类数据模型
  5.     const categoryModel = ref({
  6.         categoryName: '',
  7.         categoryAlias: ''
  8.     })
  9.     //添加分类表单校验
  10.     const rules = {
  11.         categoryName: [
  12.             { required: true, message: '请输入分类名称', trigger: 'blur' },
  13.         ],
  14.         categoryAlias: [
  15.             { required: true, message: '请输入分类别名', trigger: 'blur' },
  16.         ]
  17.     }
复制代码
添加分类按钮单击事件
  1. <el-button type="primary" @click="dialogVisible = true">添加分类</el-button>
复制代码
接口调用
在article.js中提供添加分类的函数
  1. //添加文章分类
  2. export const articleCategoryAddService = (categoryModel) => {
  3.     return request.post('/category', categoryModel)
  4. }
复制代码
在页面中调用接口
  1. //访问后台,添加文章分类
  2.     const addCategory = async ()=>{
  3.         let result = await articleCategoryAddService(categoryModel.value);
  4.         ElMessage.success(result.message? result.message:'添加成功')
  5.         //隐藏弹窗
  6.         dialogVisible.value = false
  7.         //再次访问后台接口,查询所有分类
  8.         getAllCategory()
  9.     }
  10.     <el-button type="primary" @click="addCategory"> 确认 </el-button>
复制代码
修改文章分类

修改分类弹窗页面
修改分类弹窗和新增文章分类弹窗长的一样,以是可以服用添加分类的弹窗
弹窗标题显示定义标题
  1. //弹窗标题
  2. const title=ref('')
复制代码
在弹窗上绑定标题
  1. <el-dialog v-model="dialogVisible" :title="title" width="30%">
复制代码
为添加分类按钮绑定事件
  1. <el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>
复制代码
为修改分类按钮绑定事件
  1. <el-button :icon="Edit" circle plain type="primary" @click="title='修改分类';dialogVisible=true"></el-button>
复制代码
数据回显
当点击修改分类按钮时,需要把当前这一条数据的详细信息显示到修改分类的弹窗上,这个叫回显
通过插槽的方式得到被点击按钮所在行的数据
  1. <template #default="{ row }">
  2.                     <el-button :icon="Edit" circle plain type="primary" @click="updateCategoryEcho(row)"></el-button>
  3.                     <el-button :icon="Delete" circle plain type="danger"></el-button>
  4.                 </template>
复制代码
回显函数
  1. //修改分类回显
  2.     const updateCategoryEcho = (row) => {
  3.         title.value = '修改分类'
  4.         dialogVisible.value = true
  5.         //将row中的数据赋值给categoryModel
  6.         categoryModel.value.categoryName=row.categoryName
  7.         categoryModel.value.categoryAlias=row.categoryAlias
  8.         //修改的时候必须传递分类的id,所以扩展一个id属性
  9.         categoryModel.value.id=row.id
  10.     }
复制代码
接口调用
article.js中提供修改分类的函数
  1.    //修改分类
  2.     export const articleCategoryUpdateService = (categoryModel)=>{
  3.         return request.put('/category',categoryModel)
  4.     }
复制代码
修改确定按钮的绑定事件
  1.    <span class="dialog-footer">
  2.                     <el-button @click="dialogVisible = false">取消</el-button>
  3.                     <el-button type="primary" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button>
  4.                 </span>
复制代码
调用接口完成修改的函数
  1. //修改分类
  2. const updateCategory=async ()=>{
  3.     let result = await articleCategoryUpdateService(categoryModel.value)
  4.     ElMessage.success(result.message? result.message:'修改成功')
  5.     //隐藏弹窗
  6.     dialogVisible.value=false
  7.     //再次访问后台接口,查询所有分类
  8.     getAllCategory()
  9. }
复制代码
由于现在修改和新增共用了一个数据模子,以是在点击添加分类后,有时间会显示数据,此时可以将categoryModel中的数据清空
  1.   //清空模型数据
  2.     const clearCategoryModel = ()=>{
  3.         categoryModel.value.categoryName='',
  4.         categoryModel.value.categoryAlias=''
  5.     }
复制代码
修改添加按钮的点击事件
  1. <el-button type="primary" @click="title = '添加分类'; dialogVisible = true;clearCategoryModel()">添加分类</el-button>
复制代码
删除文章分类

确认框
  1. //删除分类  给删除按钮绑定事件
  2. const deleteCategory = (row) => {
  3.     ElMessageBox.confirm(
  4.         '你确认删除该分类信息吗?',
  5.         '温馨提示',
  6.         {
  7.             confirmButtonText: '确认',
  8.             cancelButtonText: '取消',
  9.             type: 'warning',
  10.         }
  11.     )
  12.         .then(() => {
  13.             //用户点击了确认
  14.             ElMessage({
  15.                 type: 'success',
  16.                 message: '删除成功',
  17.             })
  18.         })
  19.         .catch(() => {
  20.             //用户点击了取消
  21.             ElMessage({
  22.                 type: 'info',
  23.                 message: '取消删除',
  24.             })
  25.         })
  26. }
复制代码
接口调用
article.js中提供删除分类的函数
  1.    //删除分类
  2.     export const articleCategoryDeleteService = (id) => {
  3.         return request.delete('/category?id='+id)
  4.     }
复制代码
当用户点击确认后,调用接口删除分类
  1. //删除分类
  2. const deleteCategory = (row) => {
  3.     ElMessageBox.confirm(
  4.         '你确认删除该分类信息吗?',
  5.         '温馨提示',
  6.         {
  7.             confirmButtonText: '确认',
  8.             cancelButtonText: '取消',
  9.             type: 'warning',
  10.         }
  11.     )
  12.         .then(async () => {
  13.             //用户点击了确认
  14.             let result = await articleCategoryDeleteService(row.id)
  15.             ElMessage.success(result.message?result.message:'删除成功')
  16.             //再次调用getAllCategory,获取所有文章分类
  17.             getAllCategory()
  18.         })
  19.         .catch(() => {
  20.             //用户点击了取消
  21.             ElMessage({
  22.                 type: 'info',
  23.                 message: '取消删除',
  24.             })
  25.         })
  26. }
复制代码
文章列表查询

文章列表页面组件
  1.   <script setup>
  2.     import {
  3.         Edit,
  4.         Delete
  5.     } from '@element-plus/icons-vue'
  6.    
  7.     import { ref } from 'vue'
  8.    
  9.     //文章分类数据模型
  10.     const categorys = ref([
  11.         {
  12.             "id": 3,
  13.             "categoryName": "美食",
  14.             "categoryAlias": "my",
  15.             "createTime": "2023-09-02 12:06:59",
  16.             "updateTime": "2023-09-02 12:06:59"
  17.         },
  18.         {
  19.             "id": 4,
  20.             "categoryName": "娱乐",
  21.             "categoryAlias": "yl",
  22.             "createTime": "2023-09-02 12:08:16",
  23.             "updateTime": "2023-09-02 12:08:16"
  24.         },
  25.         {
  26.             "id": 5,
  27.             "categoryName": "军事",
  28.             "categoryAlias": "js",
  29.             "createTime": "2023-09-02 12:08:33",
  30.             "updateTime": "2023-09-02 12:08:33"
  31.         }
  32.     ])
  33.    
  34.     //用户搜索时选中的分类id
  35.     const categoryId=ref('')
  36.    
  37.     //用户搜索时选中的发布状态
  38.     const state=ref('')
  39.    
  40.     //文章列表数据模型
  41.     const articles = ref([
  42.         {
  43.             "id": 5,
  44.             "title": "陕西旅游攻略",
  45.             "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  46.             "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  47.             "state": "草稿",
  48.             "categoryId": 2,
  49.             "createTime": "2023-09-03 11:55:30",
  50.             "updateTime": "2023-09-03 11:55:30"
  51.         },
  52.         {
  53.             "id": 5,
  54.             "title": "陕西旅游攻略",
  55.             "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  56.             "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  57.             "state": "草稿",
  58.             "categoryId": 2,
  59.             "createTime": "2023-09-03 11:55:30",
  60.             "updateTime": "2023-09-03 11:55:30"
  61.         },
  62.         {
  63.             "id": 5,
  64.             "title": "陕西旅游攻略",
  65.             "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  66.             "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  67.             "state": "草稿",
  68.             "categoryId": 2,
  69.             "createTime": "2023-09-03 11:55:30",
  70.             "updateTime": "2023-09-03 11:55:30"
  71.         },
  72.     ])
  73.    
  74.     //分页条数据模型
  75.     const pageNum = ref(1)//当前页
  76.     const total = ref(20)//总条数
  77.     const pageSize = ref(3)//每页条数
  78.    
  79.     //当每页条数发生了变化,调用此函数
  80.     const onSizeChange = (size) => {
  81.         pageSize.value = size
  82.     }
  83.     //当前页码发生变化,调用此函数
  84.     const onCurrentChange = (num) => {
  85.         pageNum.value = num
  86.     }
  87.     </script>
  88.     <template>
  89.         <el-card class="page-container">
  90.             <template #header>
  91.                 <div class="header">
  92.                     <span>文章管理</span>
  93.                     <div class="extra">
  94.                         <el-button type="primary">添加文章</el-button>
  95.                     </div>
  96.                 </div>
  97.             </template>
  98.             <!-- 搜索表单 -->
  99.             <el-form inline>
  100.                 <el-form-item label="文章分类:">
  101.                     <el-select placeholder="请选择" v-model="categoryId">
  102.                         <el-option
  103.                             v-for="c in categorys"
  104.                             :key="c.id"
  105.                             :label="c.categoryName"
  106.                             :value="c.id">
  107.                         </el-option>
  108.                     </el-select>
  109.                 </el-form-item>
  110.    
  111.                 <el-form-item label="发布状态:">
  112.                     <el-select placeholder="请选择" v-model="state">
  113.                         <el-option label="已发布" value="已发布"></el-option>
  114.                         <el-option label="草稿" value="草稿"></el-option>
  115.                     </el-select>
  116.                 </el-form-item>
  117.                 <el-form-item>
  118.                     <el-button type="primary">搜索</el-button>
  119.                     <el-button>重置</el-button>
  120.                 </el-form-item>
  121.             </el-form>
  122.             <!-- 文章列表 -->
  123.             <el-table :data="articles" style="width: 100%">
  124.                 <el-table-column label="文章标题" width="400" prop="title"></el-table-column>
  125.                 <el-table-column label="分类" prop="categoryId"></el-table-column>
  126.                 <el-table-column label="发表时间" prop="createTime"> </el-table-column>
  127.                 <el-table-column label="状态" prop="state"></el-table-column>
  128.                 <el-table-column label="操作" width="100">
  129.                     <template #default="{ row }">
  130.                         <el-button :icon="Edit" circle plain type="primary"></el-button>
  131.                         <el-button :icon="Delete" circle plain type="danger"></el-button>
  132.                     </template>
  133.                 </el-table-column>
  134.                 <template #empty>
  135.                     <el-empty description="没有数据" />
  136.                 </template>
  137.             </el-table>
  138.             <!-- 分页条 -->
  139.             <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]"
  140.                 layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
  141.                 @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />
  142.         </el-card>
  143.     </template>
  144.     <style lang="scss" scoped>
  145.     .page-container {
  146.         min-height: 100%;
  147.         box-sizing: border-box;
  148.    
  149.         .header {
  150.             display: flex;
  151.             align-items: center;
  152.             justify-content: space-between;
  153.         }
  154.     }
  155.     </style>
复制代码
使用中文语言包,解决分页条中文问题, 在main.js中完成
  1. import locale from 'element-plus/dist/locale/zh-cn.js'
  2.    
  3.     app.use(ElementPlus,{locale})
复制代码
文章分类数据回显
ArticleMange.vue
  1. //文章列表查询
  2. import { articleCategoryListService } from '@/api/article.js'
  3. const getArticleCategoryList = async () => {
  4.     //获取所有分类
  5.     let resultC = await articleCategoryListService();
  6.     categorys.value = resultC.data
  7. }
  8. getArticleCategoryList();
复制代码
文章列表接口调用
article.js中提供获取文章列表数据的函数
  1. //文章列表查询
  2. export const articleListService = (params) => {
  3.     return request.get('/article', { params: params })
  4. }
复制代码
ArticleManage.vue中,调用接口获取数据
  1. //文章列表查询
  2.     import { articleListService } from '@/api/article.js'
  3.     const getArticles = async () => {
  4.         let params = {
  5.             pageNum: pageNum.value,
  6.             pageSize: pageSize.value,
  7.             categoryId: categoryId.value ? categoryId.value : null,
  8.             state: state.value ? state.value : null
  9.         }
  10.         let result = await articleListService(params);
  11.         //渲染列表数据
  12.         articles.value = result.data.items
  13.         //为列表中添加categoryName属性
  14.         for(let i=0;i<articles.value.length;i++){
  15.             let article = articles.value[i];
  16.             for(let j=0;j<categorys.value.length;j++){
  17.                 if(article.categoryId===categorys.value[j].id){
  18.                     article.categoryName=categorys.value[j].categoryName
  19.                 }
  20.             }
  21.         }
  22.         //渲染总条数
  23.         total.value=result.data.total
  24.     }
  25.     getArticles()
复制代码
当分页条的当前页和每页条数发生变化,重新发送哀求获取数据
  1. //当每页条数发生了变化,调用此函数
  2. const onSizeChange = (size) => {
  3.     pageSize.value = size
  4.     getArticles()
  5. }
  6. //当前页码发生变化,调用此函数
  7. const onCurrentChange = (num) => {
  8.     pageNum.value = num
  9.     getArticles()
  10. }
复制代码
搜刮和重置
为搜刮按钮绑定单击事件,调用getArticles函数即可
  1. <el-button type="primary" @click="getArticles">搜索</el-button>
复制代码
为重置按钮绑定单击事件,清除categoryId和state的之即可
  1. <el-button @click="categoryId='';state=''">重置</el-button>
复制代码
添加文章

添加文章抽屉组件
  1. import {Plus} from '@element-plus/icons-vue'
  2. //控制抽屉是否显示
  3. const visibleDrawer = ref(false)
  4. //添加表单数据模型
  5. const articleModel = ref({
  6.     title: '',
  7.     categoryId: '',
  8.     coverImg: '',
  9.     content:'',
  10.     state:''
  11. })
  12. <!-- 抽屉 -->
  13.         <el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%">
  14.             <!-- 添加文章表单 -->
  15.             <el-form :model="articleModel" label-width="100px" >
  16.                 <el-form-item label="文章标题" >
  17.                     <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
  18.                 </el-form-item>
  19.                 <el-form-item label="文章分类">
  20.                     <el-select placeholder="请选择" v-model="articleModel.categoryId">
  21.                         <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
  22.                         </el-option>
  23.                     </el-select>
  24.                 </el-form-item>
  25.                 <el-form-item label="文章封面">
  26.                     <el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false">
  27.                         <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
  28.                         <el-icon v-else class="avatar-uploader-icon">
  29.                             <Plus />
  30.                         </el-icon>
  31.                     </el-upload>
  32.                 </el-form-item>
  33.                 <el-form-item label="文章内容">
  34.                     <div class="editor">富文本编辑器</div>
  35.                 </el-form-item>
  36.                 <el-form-item>
  37.                     <el-button type="primary">发布</el-button>
  38.                     <el-button type="info">草稿</el-button>
  39.                 </el-form-item>
  40.             </el-form>
  41.         </el-drawer>
  42. /* 抽屉样式 */
  43. .avatar-uploader {
  44.     :deep() {
  45.         .avatar {
  46.             width: 178px;
  47.             height: 178px;
  48.             display: block;
  49.         }
  50.         .el-upload {
  51.             border: 1px dashed var(--el-border-color);
  52.             border-radius: 6px;
  53.             cursor: pointer;
  54.             position: relative;
  55.             overflow: hidden;
  56.             transition: var(--el-transition-duration-fast);
  57.         }
  58.         .el-upload:hover {
  59.             border-color: var(--el-color-primary);
  60.         }
  61.         .el-icon.avatar-uploader-icon {
  62.             font-size: 28px;
  63.             color: #8c939d;
  64.             width: 178px;
  65.             height: 178px;
  66.             text-align: center;
  67.         }
  68.     }
  69. }
  70. .editor {
  71.   width: 100%;
  72.   :deep(.ql-editor) {
  73.     min-height: 200px;
  74.   }
  75. }
复制代码
为添加文章按钮添加单击事件,展示抽屉
  1. <el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>
复制代码
富文本编辑器
文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill
官网地址: https://vueup.github.io/vue-quill/
安装:
  1. npm install @vueup/vue-quill@latest --save
复制代码
导入组件和样式:
  1.   import { QuillEditor } from '@vueup/vue-quill'
  2.     import '@vueup/vue-quill/dist/vue-quill.snow.css'
复制代码
页面长使用quill组件:
  1. <quill-editor
  2.               theme="snow"
  3.               v-model:content="articleModel.content"
  4.               contentType="html"
  5.               >
  6. </quill-editor>
复制代码
样式美化:
  1. .editor {
  2.       width: 100%;
  3.       :deep(.ql-editor) {
  4.         min-height: 200px;
  5.       }
  6.     }
复制代码
文章封面图片上传
将来当点击+图标,选择本舆图片后,el-upload这个组件会自动发送哀求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步哀求,以是需要给el-upload标签添加一些属性,控制哀求的发送
auto-upload:是否自动上传
action: 服务器接口路径
name: 上传的文件字段名
headers: 设置上传的哀求头
on-success: 上传成功的回调函数
  1.   import {
  2.         Plus
  3.     } from '@element-plus/icons-vue'
  4.    
  5.     <el-form-item label="文章封面">
  6.         <el-upload class="avatar-uploader"
  7.                    :show-file-list="false"
  8.                    >
  9.             <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
  10.             <el-icon v-else class="avatar-uploader-icon">
  11.                 <Plus />
  12.             </el-icon>
  13.         </el-upload>
  14.     </el-form-item>
复制代码
注意:

  • 由于这个哀求时el-upload自动发送的异步哀求,并没有使用咱们的request.js哀求工具,以是在哀求的路ing上,需要加上/api, 这个时间哀求署理才能拦截到这个哀求,转发到后台服务器上
  • 要携带哀求头,还需要导入pinia状态才可以使用
  1.   import { useTokenStore } from '@/stores/token.js'
  2.   const tokenStore = useTokenStore();
复制代码

  • 在成功的回调函数中,可以拿到服务器相应的数据,其中有一个属性为data,对应的就是图片在阿里云oss上存储的访问地址,需要把它赋值给articleModel的coverImg属性,这样img标签就能显示这张图片了,因为img标签上通过src属性绑定了articleModel.coverImg
  1.    //上传图片成功回调
  2.       const uploadSuccess = (img) => {
  3.           //img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}
  4.           articleModel.value.coverImg=img.data
  5.       }
  6.   
复制代码
添加文章接口调用
article.js中提供添加文章函数
  1. //添加文章
  2. export const articleAddService = (articleModel)=>{
  3.     return request.post('/article',articleModel)
  4. }
复制代码
为已发布和草稿按钮绑定事件
  1. <el-form-item>
  2.     <el-button type="primary" @click="addArticle('已发布')">发布</el-button>
  3.     <el-button type="info" @click="addArticle('草稿')">草稿</el-button>
  4. </el-form-item>
复制代码
ArticleManage.vue中提供addArticle函数完成添加文章接口的调用
  1. //添加文章
  2.     const addArticle=async (state)=>{
  3.         articleModel.value.state = state
  4.         let result = await articleAddService(articleModel.value);
  5.         ElMessage.success(result.message? result.message:'添加成功')
  6.         //再次调用getArticles,获取文章
  7.         getArticles()
  8.         //隐藏抽屉
  9.         visibleDrawer.value=false
  10.     }
复制代码
顶部导航栏信息显示

在Layout.vue中,页面加载完就发送哀求,获取个人信息展示,并存储到pinia中,因为将来在个人中心中修改信息的时间还需要使用
user.js中提供获取个人信息的函数
  1. //获取个人信息
  2. export const userInfoGetService = ()=>{
  3.     return request.get('/user/userInfo');
  4. }
复制代码
src/stores/user.js中,定义个人中心状态
  1. import { defineStore } from "pinia"
  2.     import {ref} from 'vue'
  3.    
  4.     export const useUserInfoStore = defineStore('userInfo',()=>{
  5.         //1.定义用户信息
  6.         const info = ref({})
  7.         //2.定义修改用户信息的方法
  8.         const setInfo = (newInfo)=>{
  9.             info.value = newInfo
  10.         }
  11.         //3.定义清空用户信息的方法
  12.         const removeInfo = ()=>{
  13.             info.value={}
  14.         }
  15.    
  16.         return{info,setInfo,removeInfo}
  17.     },{
  18.         persist:true
  19.     })
复制代码
Layout.vue中获取个人信息,并存储到pinia中
  1.    //导入接口函数
  2.     import {userInfoGetService} from '@/api/user.js'
  3.     //导入pinia
  4.     import {useUserInfoStore} from '@/stores/user.js'
  5.     const userInfoStore = useUserInfoStore();
  6.     import {ref} from 'vue'
  7.    
  8.     //获取个人信息
  9.     const getUserInf = async ()=>{
  10.         let result = await userInfoGetService();
  11.         //存储pinia
  12.         userInfoStore.info =result.data;
  13.     }
  14.     getUserInf()
复制代码
Layout.vue的顶部导航栏中,展示昵称和头像
  1. <div>黑马程序员:<strong>{{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div>
  2. <el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" />
复制代码
下拉菜单功能

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


  • 基本资料
  • 更换头像
  • 重置密码
  • 退出登录
其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo
路由实现:
在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持同等
  1. <el-dropdown-menu>
  2.     <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
  3.     <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
  4.     <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
  5.     <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
  6. </el-dropdown-menu>
复制代码
在el-dropdown标签上绑定command事件,当有条目被点击后,会触发这个事件
  1. <el-dropdown placement="bottom-end" @command="handleCommand">
复制代码
提供handleCommand函数,参数为点击条目标command属性值
  1. //dropDown条目被点击后,回调的函数
  2.     import {useRouter} from 'vue-router'
  3.     const router = useRouter()
  4.     const handleCommand = (command)=>{
  5.         if(command==='logout'){
  6.             //退出登录
  7.             alert('退出登录')
  8.         }else{
  9.             //路由
  10.             router.push('/user/'+command)
  11.         }
  12.     }
复制代码
退出登录实现:
  1.   import {ElMessage,ElMessageBox} from 'element-plus'
  2.     import { useTokenStore } from '@/stores/token.js'
  3.     const tokenStore = useTokenStore()
  4.     const handleCommand = (command) => {
  5.         if (command === 'logout') {
  6.             //退出登录
  7.             ElMessageBox.confirm(
  8.                 '你确认退出登录码?',
  9.                 '温馨提示',
  10.                 {
  11.                     confirmButtonText: '确认',
  12.                     cancelButtonText: '取消',
  13.                     type: 'warning',
  14.                 }
  15.             )
  16.                 .then(async () => {
  17.                     //用户点击了确认
  18.                     //清空pinia中的token和个人信息
  19.                     userInfoStore.info={}
  20.                     tokenStore.token=''
  21.                     //跳转到登录页
  22.                     router.push('/login')
  23.                 })
  24.                 .catch(() => {
  25.                     //用户点击了取消
  26.                     ElMessage({
  27.                         type: 'info',
  28.                         message: '取消退出',
  29.                     })
  30.                 })
  31.         } else {
  32.             //路由
  33.             router.push('/user/' + command)
  34.         }
  35.     }
复制代码
个人中心

基本资料修改

基本资料页面组件
  1. <script setup>
  2.     import { ref } from 'vue'
  3.     const userInfo = ref({
  4.         id: 0,
  5.         username: 'zhangsan',
  6.         nickname: 'zs',
  7.         email: 'zs@163.com',
  8.     })
  9.     const rules = {
  10.         nickname: [
  11.             { required: true, message: '请输入用户昵称', trigger: 'blur' },
  12.             {
  13.                 pattern: /^\S{2,10}$/,
  14.                 message: '昵称必须是2-10位的非空字符串',
  15.                 trigger: 'blur'
  16.             }
  17.         ],
  18.         email: [
  19.             { required: true, message: '请输入用户邮箱', trigger: 'blur' },
  20.             { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
  21.         ]
  22.     }
  23.     </script>
  24.     <template>
  25.         <el-card class="page-container">
  26.             <template #header>
  27.                 <div class="header">
  28.                     <span>基本资料</span>
  29.                 </div>
  30.             </template>
  31.             <el-row>
  32.                 <el-col :span="12">
  33.                     <el-form :model="userInfo" :rules="rules" label-width="100px" size="large">
  34.                         <el-form-item label="登录名称">
  35.                             <el-input v-model="userInfo.username" disabled></el-input>
  36.                         </el-form-item>
  37.                         <el-form-item label="用户昵称" prop="nickname">
  38.                             <el-input v-model="userInfo.nickname"></el-input>
  39.                         </el-form-item>
  40.                         <el-form-item label="用户邮箱" prop="email">
  41.                             <el-input v-model="userInfo.email"></el-input>
  42.                         </el-form-item>
  43.                         <el-form-item>
  44.                             <el-button type="primary">提交修改</el-button>
  45.                         </el-form-item>
  46.                     </el-form>
  47.                 </el-col>
  48.             </el-row>
  49.         </el-card>
  50.     </template>
复制代码
表单数据回显
个人信息之前已经存储到了pinia中,只需要从pinia中获取个人信息,替换模板数据即可
  1.    import { useUserInfoStore } from '@/stores/user.js';
  2.     const userInfoStore = useUserInfoStore()
  3.     const userInfo = ref({...userInfoStore.info})
复制代码
接口调用
在src/api/user.js中提供修改基本资料的函数
  1. //修改个人信息
  2. export const userInfoUpdateService = (userInfo)=>{
  3.     return request.put('/user/update',userInfo)
  4. }
复制代码
为修改按钮绑定单击事件
  1.   <el-button type="primary" @click="updateUserInfo">提交修改</el-button>
复制代码
提供updateUserInfo函数
  1.   //修改用户信息
  2.     import {userInfoUpdateService} from '@/api/user.js'
  3.     import { ElMessage } from 'element-plus';
  4.     const updateUserInfo = async ()=>{
  5.         let result = await userInfoUpdateService(userInfo.value)
  6.         ElMessage.success(result.message? result.message:'修改成功')
  7.         //更新pinia中的数据
  8.         userInfoStore.info.nickname=userInfo.value.nickname
  9.         userInfoStore.info.email = userInfo.value.email
  10.     }
复制代码
用户头像修改

修改头像页面组件
  1. <script setup>
  2. import { Plus, Upload } from '@element-plus/icons-vue'
  3. import {ref} from 'vue'
  4. import avatar from '@/assets/default.png'
  5. const uploadRef = ref()
  6. //用户头像地址
  7. const imgUrl= avatar
  8. </script>
  9. <template>
  10.     <el-card class="page-container">
  11.         <template #header>
  12.             <div class="header">
  13.                 <span>更换头像</span>
  14.             </div>
  15.         </template>
  16.         <el-row>
  17.             <el-col :span="12">
  18.                 <el-upload
  19.                     ref="uploadRef"
  20.                     class="avatar-uploader"
  21.                     :show-file-list="false"
  22.                     >
  23.                     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  24.                     <img v-else src="avatar" width="278" />
  25.                 </el-upload>
  26.                 <br />
  27.                 <el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">
  28.                     选择图片
  29.                 </el-button>
  30.                 <el-button type="success" :icon="Upload" size="large">
  31.                     上传头像
  32.                 </el-button>
  33.             </el-col>
  34.         </el-row>
  35.     </el-card>
  36. </template>
  37. <style lang="scss" scoped>
  38. .avatar-uploader {
  39.     :deep() {
  40.         .avatar {
  41.             width: 278px;
  42.             height: 278px;
  43.             display: block;
  44.         }
  45.         .el-upload {
  46.             border: 1px dashed var(--el-border-color);
  47.             border-radius: 6px;
  48.             cursor: pointer;
  49.             position: relative;
  50.             overflow: hidden;
  51.             transition: var(--el-transition-duration-fast);
  52.         }
  53.         .el-upload:hover {
  54.             border-color: var(--el-color-primary);
  55.         }
  56.         .el-icon.avatar-uploader-icon {
  57.             font-size: 28px;
  58.             color: #8c939d;
  59.             width: 278px;
  60.             height: 278px;
  61.             text-align: center;
  62.         }
  63.     }
  64. }
  65. </style>
复制代码
头像回显
从pinia中读取用户的头像数据
  1. //读取用户信息
  2.     import {ref} from 'vue'
  3.     import {useUserInfoStore} from '@/stores/user.js'
  4.     const userInfoStore = useUserInfoStore()
  5.     const imgUrl=ref(userInfoStore.info.userPic)
复制代码
img标签上绑定图片地址
  1. <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  2. <img v-else src="@/assets/avatar.jpg" width="278" />
复制代码
头像上传
为el-upload指定属性值,分别有:


  • ​ action: 服务器接口路径
  • ​ headers: 设置哀求头,需要携带token
  • ​ on-success: 上传成功的回调函数
  • ​ name: 上传图片的字段名称
  1. <el-upload
  2.            class="avatar-uploader"
  3.            :show-file-list="false"
  4.            :auto-upload="true"
  5.            action="/api/upload"
  6.            name="file"
  7.            :headers="{'Authorization':tokenStore.token}"
  8.            :on-success="uploadSuccess"
  9.            >
  10.     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  11.     <img v-else src="@/assets/avatar.jpg" width="278" />
  12. </el-upload>
复制代码
提供上传成功的回调函数
  1.   //读取token信息
  2.     import {useTokenStore} from '@/stores/token.js'
  3.     const tokenStore = useTokenStore()
  4.    
  5.     //图片上传成功的回调
  6.     const uploadSuccess = (result)=>{
  7.         //回显图片
  8.         imgUrl.value = result.data
  9.     }
复制代码
外部触发图片选择
​ 需要获取到el-upload组件,然后再通过$el.querySelector(‘input’)获取到el-upload对应的元素,触发click事件
  1.    //获取el-upload元素
  2.     const uploadRef = ref()
  3.    
  4.    
  5.     <el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">
  6.         选择图片
  7.     </el-button>
复制代码
接口调用
在user.js中提供修改头像的函数
  1. //修改头像
  2. export const userAvatarUpdateService=(avatarUrl)=>{
  3.     let params = new URLSearchParams();
  4.     params.append('avatarUrl',avatarUrl)
  5.     return request.patch('/user/updateAvatar',params)
  6. }
复制代码
为【上传头像】按钮绑定单击事件
  1. <el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
  2.     上传头像
  3. </el-button>
复制代码
提供updateAvatar函数,完成头像更新
  1. //调用接口,更新头像url
  2. import {userAvatarUpdateService} from '@/api/user.js'
  3. import {ElMessage} from 'element-plus'
  4. const updateAvatar = async ()=>{
  5.     let result = await userAvatarUpdateService(imgUrl.value)
  6.     ElMessage.success(result.message? result.message:'修改成功')
  7.     //更新pinia中的数据
  8.     userInfoStore.info.userPic=imgUrl.value
  9. }
  10.   
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

海哥

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表