当前位置: 首页 > news >正文

【SpringBoot3+Vue3】五【完】【实战篇】-前端(配合后端)

目录

一、环境准备

1、创建Vue工程

2、安装依赖 

2.1 安装项目所需要的vue依赖 

2.2 安装element-plus依赖

2.2.1 安装

2.2.2 项目导入element-plus

2.3 安装axios依赖

2.4 安装sass依赖

3、目录调整

3.1 删除部分默认目录下文件

3.1.1 src/components下自动生成的内容(删除文件夹下全部内容)

3.1.2 src/assets 下自动生成的内容(删除文件夹下全部内容) 

3.2 api、utils、views目录处理

3.2.1 src下新建目录api、utils、views

 3.2.2 utils下新增request.js文件

3.3 将资料中的静态资源拷贝到assets目录下

3.4 修改app.vue生成的内容

4、启动项目验证

二、注册功能

1、页面搭建 

1.1 views下新建Login.vue

1.2 App.vue导入Login.vue

1.3 查看结果

2、为注册页面绑定数据与事件

2.1 定义数据模型Login.vue

 2.2 注册页面Login.vue表单校验

2.3 验证

3、注册页面后台接口调用 

3.1 启动后端服务

3.2 api目录下新建user.js文件

3.3 Login.vue页面完成调用

4、跨域问题解决

4.1 request.js

4.2 vite.config.js

5、测试验证

三、登录功能

1、登录Login.vue绑定数据 

2、 登录Login.vue数据校验

3、登录页面后端接口调用

3.1 user.js提供登录调用接口

3.2 Login.vue调用后端接口

4、测试验证

5、优化登录表单与注册表单数据显示问题(Login.vue)

四、优化axios响应拦截器与alert

1、axios响应拦截器

2.1 request.js 

2.2 优化Login.vue

2、使用element-plus优化alert

2.1 request.js引用ElMessage 组件处理

2.2  Login.vue引用ElMessage 组件处理

五、主页面布局 

1、Layout页面

1.1 views下新增Layout.vue

 1.2 App.vue导入Layout.vue

2、路由

 2.1 安装vue-router

2.2 创建路由器index.js

2.3 在main.js使用vue-router

2.4 App.vue中声明router-view标签

2.5  Login.vue优化登录成功跳转

2.6 测试验证

3、子路由

3.1 views下创建vue文件 

3.1.1 ArticleCategory.vue

3.1.2 ArticleManage.vue

3.1.3 UserAvatar.vue

3.1.4 UserInfo.vue

3.1.5 UserResetPassword.vue

3.2 index.js

3.3 Layout.vue声明router-view标签

3.4 测试验证

六、文章分类 

1、列表查询

1.1 article.js

1.2 ArticleCategory.vue

2、Pinia状态管理库

2.1 安装pinia

2.2 main.js导入pinia

2.3 定义store

2.4 使用Store

2.5 测试验证

 3、 使用axios请求拦截器解决token繁琐问题

​编辑 3.1 request.js添加请求拦截器

3.2 article.js移除之前添加的请求头

4、pinia-persistedstate-plugin持久化插件

4.1 安装

4.2 pinia中使用persist插件

4.3 在创建定义状态是配置持久化

5、未登录统一处理

6、添加文章分类

6.1  ArticleCategory.vue

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

6.3 测试验证

7、修改文章分类 

7.1 修改分类弹窗页面

7.1.1 弹窗标题显示  ArticleCategory.vue

7.1.2 在弹窗上绑定标题 ArticleCategory.vue

7.1.3 为添加分类按钮绑定事件 ArticleCategory.vue

7.1.4 为修改分类按钮绑定事件 ArticleCategory.vue

7.2 修改数据回显 ArticleCategory.vue

7.3 修改文章分类接口调用

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

7.3.2 修改确定按钮的绑定事件 ArticleCategory.vue

7.3.3 调用接口完成修改的函数  ArticleCategory.vue

7.3.4 优化添加时数据回显

8、删除文章分类

 8.1 删除接口

8.2 删除确认框

 8.3 为删除按钮绑定事件

8.4 调用删除接口

8.5 测试验证

七、文章列表 

1、 文章列表查询

 1.1 文章列表页面组件ArticleManage.vue

1.2 解决分页控件英文显示问题main.js

1.3 文章分类数据回显ArticleMange.vue  

1.4 文章列表接口调用

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

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

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

2 、搜索和重置

3、添加文章

3.1 添加文章抽屉组件

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

3.3 富文本编辑器

3.3.1 安装:

3.3.2 导入组件和样式:

3.3.3 页面使用quill组件:​

3.3.4 样式美化:

3.4 文章封面图片上传

3.5 添加文章接口调用

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

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

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

4、编辑文章

4.1 修改文章抽屉页面

4.1.1 抽屉标题显示 定义标题

4.1.2 在抽屉上绑定标题

4.1.3 为添加文章按钮绑定事件

4.1.4 为修改文章按钮绑定事件

4.2 数据回显

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

4.2.2 回显函数

4.3 修改文章接口调用

4.3.1 article.js中提供修改文章的函数

4.3.2 修改发布与草稿按钮的绑定事件

4.3.3 调用接口完成修改的函数

5、删除文章

5.1 接口调用

5.2 为删除按钮绑定事件

5.3 添加删除弹窗,当用户点击确认后,调用接口删除分类

八、顶部导航栏信息展示

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

2、 src/stores/userInfo.js中,定义个人中心状态

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

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

九 、页面右上角下拉菜单el-dropdown中功能实现

1、路由实现:

2、 退出登录实现:

十、基本资料修改

1、 基本资料页面组件UserInfo.vue

2、 表单数据回显

3、 接口调用

十一、修改头像

1、 修改头像页面组件UserAvatar.vue

2、 头像回显

3、 头像上传

4、 上传头像接口调用

十二、 重置密码

1、user.js 重置密码

 2、UserResetPassword.vue


前言:本项目的终结篇,是一名vue3完成本次课程的前端页面。

一、环境准备

1、创建Vue工程

npm init vue@latest# 项目名称spring3vue3project

2、安装依赖 

进入到创建项目目录

2.1 安装项目所需要的vue依赖 

# 项目所需要的vue依赖
npm install

2.2 安装element-plus依赖

2.2.1 安装
# 安装element-plus依赖
npm install element-plus --save

2.2.2 项目导入element-plus

打开项目 

code .

修改src/main.js 

import './assets/main.scss'import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'import App from './App.vue'const app = createApp(App)
app.use(ElementPlus)app.mount('#app')

2.3 安装axios依赖

npm install axios

2.4 安装sass依赖

# 安装sass依赖 好像是关于css的
npm install sass -D

3、目录调整

直接进到项目文件夹下操作

3.1 删除部分默认目录下文件

3.1.1 src/components下自动生成的内容(删除文件夹下全部内容)

3.1.2 src/assets 下自动生成的内容(删除文件夹下全部内容) 

3.2 api、utils、views目录处理

3.2.1 src下新建目录api、utils、views

 3.2.2 utils下新增request.js文件
// 定制请求实例//导入axios
import axios from 'axios'; //定义一个变量,记录公共的前缀baseURL
const baseURL = 'http://localhost:8080'
const instance = axios.create({baseURL})// 添加响应拦截器
instance.interceptors.response.use(result=>{return result.data;},err=>{alert('服务异常')return Promise.reject(err);//异步的状态转化成失败的状态}
)export default instance;

3.3 将资料中的静态资源拷贝到assets目录下

如需,关注,发私信

3.4 修改app.vue生成的内容

删除app.vue中自动生成的内容

<script setup></script><template>
春天的菠菜</template><style scoped></style>

4、启动项目验证

npm run dev

二、注册功能

 

1、页面搭建 

1.1 views下新建Login.vue

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)
</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister"><el-form-item><h1>注册</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

1.2 App.vue导入Login.vue

<script setup>
import LoginVue from '@/views/Login.vue'</script><template>
<LoginVue/>
</template><style scoped></style>

1.3 查看结果

2、为注册页面绑定数据与事件

2.1 定义数据模型Login.vue

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData"><el-form-item><h1>注册</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

 2.2 注册页面Login.vue表单校验

表单校验可看官网

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 2.1  :model="registerData"表单声明属性 --> <!-- 2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!-- 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!-- 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

2.3 验证

3、注册页面后台接口调用 

3.1 启动后端服务

启动前面章节后端服务以及redis 

3.2 api目录下新建user.js文件

//导入request.js请求工具
import request from '@/utils/request.js'// 提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}

3.3 Login.vue页面完成调用

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
// 3.3 导入注册的接口
import { userRegisterService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}//3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')}
}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 2.1  :model="registerData"表单声明属性 --> <!-- 2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!-- 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!-- 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

验证,存在跨域问题

4、跨域问题解决

4.1 request.js

// 定制请求实例//导入axios  npm install axios
import axios from 'axios';//定义一个变量,记录公共的前缀baseURL
// const baseURL = 'http://localhost:8080'  
const baseURL = '/api'          // 注释上面代码,解决跨域问题
const instance = axios.create({baseURL})// 添加响应拦截器
instance.interceptors.response.use(result=>{return result.data;},err=>{alert('服务异常')return Promise.reject(err);//异步的状态转化成失败的状态}
)export default instance;

4.2 vite.config.js

import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}},// server 部分都是为了解决跨域问题server:{proxy:{'/api':{//获取路径中包含了/api的请求target:'http://localhost:8080',//后台服务所在的源changeOrigin:true,//修改源rewrite:(path)=>path.replace(/^\/api/,'')///api替换为''}}}
})

5、测试验证

三、登录功能

 

1、登录Login.vue绑定数据 

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口
import { userRegisterService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')}
}// 三 登录 1 绑定数据 复用注册表单的数据模型</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

2、 登录Login.vue数据校验

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口
import { userRegisterService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')}
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>               <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

3、登录页面后端接口调用

3.1 user.js提供登录调用接口

//导入request.js请求工具
import request from '@/utils/request.js'// 二  注册函数  提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}// 三  登录函数 提供调用登录接口的函数
export const userLoginService = (loginData)=>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in loginData){params.append(key,loginData[key]);}return request.post('/user/login',params);}

3.2 Login.vue调用后端接口

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')}
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false">← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true">注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

4、测试验证

 

5、优化登录表单与注册表单数据显示问题(Login.vue)

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')}
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}}
// 三 登录 4 定义函数清空数据模型数据(将数据清空)
const clearRegisterDate = ()=>{registerData.value={username:'',password:'',rePassword:''}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false;clearRegisterDate()"> <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true;clearRegisterDate()">  <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

四、优化axios响应拦截器与alert

1、axios响应拦截器

2.1 request.js 

(上图右侧的文件名错误)

// 定制请求实例//导入axios  npm install axios
import axios from 'axios';//定义一个变量,记录公共的前缀baseURL
// const baseURL = 'http://localhost:8080'  
const baseURL = '/api'          // 注释上面代码,解决跨域问题
const instance = axios.create({baseURL})// 添加响应拦截器
instance.interceptors.response.use(result=>{// 判断业务状态码if(result.data.code === 1){//成功,正常返回数据return result.data;}// 操作失败alert(result.data.msg?result.data.msg : '服务异常')// 异步操作的状态转换为失败return Promise.reject(result.data)},err=>{alert('服务异常')return Promise.reject(err);//异步的状态转化成失败的状态}
)export default instance;

2.2 优化Login.vue

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')} */alert(result.msg?result.msg : '注册成功')
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}*/alert(result.msg?result.msg : '登录成功')}
// 三 登录 4 定义函数清空数据模型数据(将数据清空)
const clearRegisterDate = ()=>{registerData.value={username:'',password:'',rePassword:''}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false;clearRegisterDate()"> <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true;clearRegisterDate()">  <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

2、使用element-plus优化alert

2.1 request.js引用ElMessage 组件处理

// 定制请求实例//导入axios  npm install axios
import axios from 'axios';
// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';//定义一个变量,记录公共的前缀baseURL
// const baseURL = 'http://localhost:8080'  
const baseURL = '/api'          // 注释上面代码,解决跨域问题
const instance = axios.create({baseURL})// 添加响应拦截器
instance.interceptors.response.use(result=>{// 四  4.1 判断业务状态码if(result.data.code === 1){//成功,正常返回数据return result.data;}// 操作失败// alert(result.data.msg?result.data.msg : '服务异常')  // 四 4.2 优化alertElMessage.error(result.data.msg?result.data.msg : '服务异常')// 异步操作的状态转换为失败return Promise.reject(result.data)},err=>{alert('服务异常')return Promise.reject(err);//异步的状态转化成失败的状态}
)export default instance;

2.2  Login.vue引用ElMessage 组件处理

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')} */// 四 4.2 优化alert// alert(result.msg?result.msg : '注册成功')ElMessage.success(result.msg?result.msg : '注册成功')
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}*/// 四 4.2 优化alert// alert(result.msg?result.msg : '登录成功')ElMessage.success(result.msg?result.msg : '登录成功')}
// 三 登录 4 定义函数清空数据模型数据(将数据清空)
const clearRegisterDate = ()=>{registerData.value={username:'',password:'',rePassword:''}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false;clearRegisterDate()"> <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true;clearRegisterDate()">  <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

五、主页面布局 

1、Layout页面

1.1 views下新增Layout.vue

<script setup>
import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
</script><template><!-- element-plus 中的容器 --><el-container class="layout-container"><!-- 左侧菜单 --><el-aside width="200px"><div class="el-aside__logo"></div><!-- element-plus 菜单标签 --><el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"router><el-menu-item ><el-icon><Management /></el-icon><span>文章分类</span></el-menu-item><el-menu-item ><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-item ><el-icon><User /></el-icon><span>基本资料</span></el-menu-item><el-menu-item ><el-icon><Crop /></el-icon><span>更换头像</span></el-menu-item><el-menu-item ><el-icon><EditPen /></el-icon><span>重置密码</span></el-menu-item></el-sub-menu></el-menu></el-aside><!-- 右侧主区域 --><el-container><!-- 头部区域 --><el-header><div>编码集中营:<strong>春天的菠菜</strong></div><el-dropdown placement="bottom-end"><span class="el-dropdown__box"><el-avatar :src="avatar" /><el-icon><CaretBottom /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><!-- 中间区域 --><el-main><div style="width: 1290px; height: 570px;border: 1px solid red;">内容展示区</div></el-main><!-- 底部区域 --><el-footer>国际大事件 ©2023 Created by 春天的菠菜</el-footer></el-container></el-container>
</template><style lang="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>

 1.2 App.vue导入Layout.vue

<script setup>
import LoginVue from '@/views/Login.vue'
import LayoutVue from '@/views/Layout.vue';</script><template>
<LoginVue/>
<!-- 五 LayoutVue-->
<LayoutVue/>
</template><style scoped></style>

2、路由

 

 2.1 安装vue-router

npm install vue-router@4

2.2 创建路由器index.js

src下新增router文件夹,在router下新增index.js

import { createRouter, createWebHistory } from 'vue-router'//导入组件
import LoginVue from '@/views/Login.vue'
import LayoutVue from '@/views/Layout.vue'//定义路由关系
const routes = [{ path: '/login', component: LoginVue },{ path: '/', component: LayoutVue }  
]//创建路由器
const router = createRouter({history: createWebHistory(),routes: routes
})//导出路由
export default router

2.3 在main.js使用vue-router

import './assets/main.scss'  // 本项目使用sassimport { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//五 2.3 
import router from '@/router'import App from './App.vue'const app = createApp(App)
//五 2.3 
app.use(router)app.use(ElementPlus)
app.mount('#app')

2.4 App.vue中声明router-view标签

<script setup>
// 五  2.4
// import LoginVue from '@/views/Login.vue'
// import LayoutVue from '@/views/Layout.vue';</script><template><!-- 五  2.4 --><router-view></router-view><!-- 五  2.4 -->
<!-- <LoginVue/> -->
<!-- 五 1.1 LayoutVue-->
<LayoutVue/>
</template><style scoped></style>

2.5  Login.vue优化登录成功跳转

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';// 五 2.5 导入路由
import {useRouter} from 'vue-router'// 五 2.5 调用路由
const router = useRouter()//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')} */// 四 4.2 优化alert// alert(result.msg?result.msg : '注册成功')ElMessage.success(result.msg?result.msg : '注册成功')
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}*/// 四 4.2 优化alert// alert(result.msg?result.msg : '登录成功')ElMessage.success(result.msg?result.msg : '登录成功')// 五 2.5 跳转首页 借助于路由router.push('/')}
// 三 登录 4 定义函数清空数据模型数据(将数据清空)
const clearRegisterDate = ()=>{registerData.value={username:'',password:'',rePassword:''}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false;clearRegisterDate()"> <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true;clearRegisterDate()">  <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

2.6 测试验证

登录页面:   http://127.0.0.1:5173/login

3、子路由

 

3.1 views下创建vue文件 

 

3.1.1 ArticleCategory.vue
<template>文章分类
</template>
3.1.2 ArticleManage.vue
<template>文章管理
</template>
3.1.3 UserAvatar.vue
<template>更换头像
</template>
3.1.4 UserInfo.vue
<template>基本资料
</template>
3.1.5 UserResetPassword.vue
<template>重置密码
</template>

3.2 index.js

import { createRouter, createWebHistory } from 'vue-router'//导入组件
import LoginVue from '@/views/Login.vue'
import LayoutVue from '@/views/Layout.vue'import ArticleCategoryVue from '@/views/article/ArticleCategory.vue'
import ArticleManageVue from '@/views/article/ArticleManage.vue'
import UserAvatarVue from '@/views/user/UserAvatar.vue'
import UserInfoVue from '@/views/user/UserInfo.vue'
import UserResetPasswordVue from '@/views/user/UserResetPassword.vue'//定义路由关系
const routes = [{ 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 }]}
]//创建路由器
const router = createRouter({history: createWebHistory(),routes: routes
})//导出路由
export default router

3.3 Layout.vue声明router-view标签

<script setup>
import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
</script><template><!-- element-plus 中的容器 --><el-container class="layout-container"><!-- 左侧菜单 --><el-aside width="200px"><div class="el-aside__logo"></div><!-- element-plus 菜单标签 --><el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"router><el-menu-item index="/article/category">  <!-- 五 3.3  --><el-icon><Management /></el-icon><span>文章分类</span></el-menu-item><el-menu-item index="/article/manage">  <!-- 五 3.3  --><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-item index="/user/info">  <!-- 五 3.3  --><el-icon><User /></el-icon><span>基本资料</span></el-menu-item><el-menu-item index="/user/avatar">  <!-- 五 3.3  --><el-icon><Crop /></el-icon><span>更换头像</span></el-menu-item><el-menu-item index="/user/resetPassword">  <!-- 五 3.3  --><el-icon><EditPen /></el-icon><span>重置密码</span></el-menu-item></el-sub-menu></el-menu></el-aside><!-- 右侧主区域 --><el-container><!-- 头部区域 --><el-header><div>编码集中营:<strong>春天的菠菜</strong></div><el-dropdown placement="bottom-end"><span class="el-dropdown__box"><el-avatar :src="avatar" /><el-icon><CaretBottom /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><!-- 中间区域 --><el-main><!-- 五 3.3  --><!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">内容展示区</div> --><router-view></router-view></el-main><!-- 底部区域 --><el-footer>国际大事件 ©2023 Created by 春天的菠菜</el-footer></el-container></el-container>
</template><style lang="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>

3.4 测试验证

六、文章分类 

1、列表查询

1.1 article.js

api下新建article.js

//导入request.js请求工具
import request from '@/utils/request.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    return request.get('/category');}

1.2 ArticleCategory.vue

<script setup>
// 六 1.2 声明一个异步函数
import {articleCategoryListService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary">添加分类</el-button></div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" ></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

此时页面访问存在token问题

2、Pinia状态管理库

2.1 安装pinia

npm install pinia

2.2 main.js导入pinia

在main.js中,引入pinia,创建pinia实例,并调用vue应用实例的use方法使用pinia

import './assets/main.scss'  // 本项目使用sassimport { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//五 2.3 
import router from '@/router'import App from './App.vue'
//六 2.2
import {createPinia} from 'pinia'const app = createApp(App)
//六 2.2
const pinia = createPinia();
app.use(pinia)
//五 2.3 
app.use(router)app.use(ElementPlus)
app.mount('#app')

2.3 定义store

在src/stores目录下定义token.js

//定义store
import {defineStore} from 'pinia'
import {ref} from 'vue'
/* 第一个参数:名字,唯一性第二个参数:函数,函数的内部可以定义状态的所有内容返回值: 函数
*/
export const useTokenStore = defineStore('token',()=>{//定义状态的内容//1.响应式变量const token = ref('')//2.定义一个函数,修改token的值const setToken = (newToken)=>{token.value = newToken}//3.函数,移除token的值const removeToken = ()=>{token.value=''}return {token,setToken,removeToken}
});

2.4 使用Store

在需要使用状态的地方,导入@/stores/*.js , 使用即可

在Login.vue中导入@/stores/token.js, 并且当用户登录成功后,将token保存pinia中

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
//二 注册 3.3 导入注册的接口 //三 登录 3.3 导入登录的接口
import { userRegisterService,userLoginService} from '@/api/user.js'// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';// 五 2.5 导入路由
import {useRouter} from 'vue-router'// 六  2.4 导入token状态
import { useTokenStore } from '@/stores/token.js'// 五 2.5 调用路由
const router = useRouter()// 六  2.4   调用useTokenStore得到状态
const tokenStore = useTokenStore();//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)//二 注册 2.1定义数据模型const registerData = ref({username:'',password:'',rePassword:''
})//二 注册  2.2自定义rePassword需要自定义校验规则校验规则的函数,三个参rule 规则,value 值,callback 回调函数
const checkRePassword = (rule,value,callback)=>{if(value === '' ){callback(new Error('请再次确认密码!'))}else if(value !== registerData.value.password){callback(new Error('请确保两次密码输入一致性!'))}else{callback()  //校验通过}
}//二 注册  2.2 定义表单校验规则,rePassword需要自定义校验规则
const rules = {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'}]
}// 二 注册  3.3 调用后台接口,完成注册
const  register = async()=>{// registerData 是一个响应式对象,如果要获取值需要.valuelet result = await userRegisterService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//注册成功alert(result.msg?result.msg : '注册成功')}else{// 注册失败alert('注册失败')} */// 四 4.2 优化alert// alert(result.msg?result.msg : '注册成功')ElMessage.success(result.msg?result.msg : '注册成功')
}// 三 登录 1 绑定数据 复用注册表单的数据模型
// 三 登录 2  数据校验 复用注册表单的数据校验
// 三 登录 3  登录函数
const login  = async()=>{ //调用接口完成登录let result = await userLoginService(registerData.value);/* 使用axios的 request.js 统一处理了,优化这里代码if(result.code ===1){//登录成功alert(result.msg?result.msg : '登录成功')}else{// 登录失败alert('登录失败')}*/// 四 4.2 优化alert// alert(result.msg?result.msg : '登录成功')ElMessage.success(result.msg?result.msg : '登录成功')// 六  2.4  保存token存储到piniatokenStore.setToken(result.data)// 五 2.5 跳转首页 借助于路由router.push('/')}
// 三 登录 4 定义函数清空数据模型数据(将数据清空)
const clearRegisterDate = ()=>{registerData.value={username:'',password:'',rePassword:''}}</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">  <!-- 二 注册2.1  :model="registerData"表单声明属性 --> <!-- 二 注册2.2  :rules="rules"绑定校验 --><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username">  <!--二 注册 2.2 prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>  <!--二 注册 2.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="password"> <!-- 二 注册2.2 prop="password"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>  <!-- 二 注册2.1 v-model="registerData.password"绑定属性 --></el-form-item><el-form-item prop="rePassword">  <!-- 2.2 prop="rePassword"绑定校验 --><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>  <!-- 二 注册2.1 v-model="registerData.rePassword"绑定属性 --></el-form-item><!-- 注册按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="register"> <!-- 二 注册3.3  @click="register"  绑定注册单击事件 -->注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false;clearRegisterDate()"> <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->← 返回</el-link></el-form-item></el-form><!-- 登录表单 --><el-form ref="form" size="large" autocomplete="off" v-else model="registerDate" rules="rules">  <!-- 三 登录 3.1  :model="registerData"表单声明属性,复用注册 --> <!-- 三 登录 3.2  :rules="rules"绑定校验 --><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input> <!--三 登录 3.1 v-model="registerData.username"绑定属性 --></el-form-item><el-form-item prop="username"> <!--三 登录 3.2  prop="username"绑定校验 --><el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input> <!--三 登录 3.1 v-model="registerData.password"绑定属性 --></el-form-item>                <el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true;clearRegisterDate()">  <!--三 登录 4  调用函数clearRegisterDate() 清空数据 -->注册 →</el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="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>

在article.js中导入@/stores/token.js, 从pinia中获取到存储的token,在发起查询文章分类列表的时候把token通过请求头的形式携带给服务器

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态const tokenStore = useTokenStore()//通过请求头Authorization携带tokenreturn request.get('/category', { headers: { 'Authorization': tokenStore.token } });}

2.5 测试验证

 3、 使用axios请求拦截器解决token繁琐问题

当进入主页后,将来要与后台交互,都需要携带token,如果每次请求都写这样的代码,将会比较繁琐,此时可以将携带token的代码通过请求拦截器统一处理

 3.1 request.js添加请求拦截器

在 src/util/request.js中

// 定制请求实例//导入axios  npm install axios
import axios from 'axios';
// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';// 六  3 导入token状态
import { useTokenStore } from '@/stores/token.js';//定义一个变量,记录公共的前缀baseURL
// const baseURL = 'http://localhost:8080'  
const baseURL = '/api'          // 注释上面代码,解决跨域问题
const instance = axios.create({baseURL})
//六  3 添加请求拦截器
instance.interceptors.request.use((config)=>{//在发送请求之前做什么let tokenStore = useTokenStore()//如果token中有值,在携带if(tokenStore.token){config.headers.Authorization=tokenStore.token}return config},(err)=>{//如果请求错误做什么Promise.reject(err)}
)// 添加响应拦截器
instance.interceptors.response.use(result=>{// 四  4.1 判断业务状态码if(result.data.code === 1){//成功,正常返回数据return result.data;}// 操作失败// alert(result.data.msg?result.data.msg : '服务异常')  // 四 4.2 优化alertElMessage.error(result.data.msg?result.data.msg : '服务异常')// 异步操作的状态转换为失败return Promise.reject(result.data)},err=>{alert('服务异常')return Promise.reject(err);//异步的状态转化成失败的状态}
)export default instance;

3.2 article.js移除之前添加的请求头

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}

4、pinia-persistedstate-plugin持久化插件

4.1 安装

npm install pinia-persistedstate-plugin

4.2 pinia中使用persist插件

在main.js中

import './assets/main.scss'  // 本项目使用sassimport { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//五 2.3 
import router from '@/router'import App from './App.vue'
//六 2.2
import {createPinia} from 'pinia'// 六  4.2 导入持久化插件
import {createPersistedState} from'pinia-persistedstate-plugin'const app = createApp(App)
//六 2.2
const pinia = createPinia();
// 六  4.2
const persist = createPersistedState()
//六  4.2 pinia使用持久化插件
pinia.use(persist)
app.use(pinia)
//五 2.3 
app.use(router)app.use(ElementPlus)
app.mount('#app')

4.3 在创建定义状态是配置持久化

在src/stores/token.js中

//定义store
import {defineStore} from 'pinia'
import {ref} from 'vue'
/* 第一个参数:名字,唯一性第二个参数:函数,函数的内部可以定义状态的所有内容返回值: 函数
*/
export const useTokenStore = defineStore('token',()=>{//定义状态的内容//1.响应式变量const token = ref('')//2.定义一个函数,修改token的值const setToken = (newToken)=>{token.value = newToken}//3.函数,移除token的值const removeToken = ()=>{token.value=''}return {token,setToken,removeToken}
},{persist:true//  六 4.3持久化存储
});

5、未登录统一处理

在后续访问接口时,如果没有登录,则前端不携带token,后台服务器会返回响应状态码401,代表未登录,此时可以在axios的响应拦截器中,统一对未登录的情况做处理request.js

// 定制请求实例//导入axios  npm install axios
import axios from 'axios';
// 四 4.2 element-plus封装的组件
import { ElMessage } from 'element-plus';// 六  3.1 导入token状态
import { useTokenStore } from '@/stores/token.js';// 六 5  导入路由跳转到首页
import router from '@/router'//定义一个变量,记录公共的前缀baseURL
// const baseURL = 'http://localhost:8080'  
const baseURL = '/api'          // 注释上面代码,解决跨域问题
const instance = axios.create({baseURL})
//六  3 添加请求拦截器
instance.interceptors.request.use((config)=>{//在发送请求之前做什么let tokenStore = useTokenStore()//如果token中有值,在携带if(tokenStore.token){config.headers.Authorization=tokenStore.token}return config},(err)=>{//如果请求错误做什么Promise.reject(err)}
)// 添加响应拦截器
instance.interceptors.response.use(result=>{// 四  4.1 判断业务状态码if(result.data.code === 1){//成功,正常返回数据return result.data;}// 操作失败// alert(result.data.msg?result.data.msg : '服务异常')  // 四 4.2 优化alert// ElMessage.error(result.data.msg?result.data.msg : '服务异常')ElMessage.error(result.data.msg || '服务异常')// 异步操作的状态转换为失败return Promise.reject(result.data)},err=>{// 六 5  如果响应状态码时401,代表未登录,给出对应的提示,并跳转到登录页if(err.response.status===401){ElMessage.error('请先登录!')router.push('/login')}else{ElMessage.error('服务异常');}return Promise.reject(err);//异步的状态转化成失败的状态
}
)export default instance;

6、添加文章分类

6.1  ArticleCategory.vue

添加弹窗分类、数据模型和校验规则、添加分类按钮单击事件,确认按钮单击事件、在页面中调用接口

<script setup>// 六 6.2 element-plus封装的组件
import { ElMessage } from 'element-plus';
// 六 1.2 articleCategoryListService声明一个异步函数  6.2articleCategoryAddService
import {articleCategoryListService,articleCategoryAddService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();//  六  6.1  控制添加分类弹窗
const dialogVisible = ref(false)//六  6.1 添加分类数据模型
const categoryModel = ref({categoryName: '',categoryAlias: ''
})
//六  6.1   添加分类表单校验
const rules = {categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' },],categoryAlias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },]
}
// 六 6.1 调用接口添加表单
const addCategory = async ()=>{let result = await articleCategoryAddService(categoryModel.value);ElMessage.success(result.message? result.message:'添加成功')//隐藏弹窗dialogVisible.value = false//再次访问后台接口,查询所有分类articleCategoryList()
}</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary" @click="dialogVisible = true">添加分类</el-button>  <!--  六 6.1  @click="dialogVisible = true"绑定点击事件--></div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="创建时间" prop="createTime"></el-table-column><el-table-column label="更新时间" prop="updateTime"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" ></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!--  六  6.1 添加分类弹窗 --><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" @click="addCategory"> 确认 </el-button> <!--  六 6.1 @click="addCategory"绑定点击事件--></span></template></el-dialog></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

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

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}

6.3 测试验证

7、修改文章分类 

分析编辑与新增的弹窗页面可以复用,优化实施

7.1 修改分类弹窗页面

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

7.1.1 弹窗标题显示  ArticleCategory.vue

定义标题

//弹窗标题
const title=ref('')
7.1.2 在弹窗上绑定标题 ArticleCategory.vue
 <el-dialog v-model="dialogVisible" :title="title" width="30%">
7.1.3 为添加分类按钮绑定事件 ArticleCategory.vue
<el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>
7.1.4 为修改分类按钮绑定事件 ArticleCategory.vue
<el-button :icon="Edit" circle plain type="primary" @click="title='修改分类';dialogVisible=true"></el-button>

7.2 修改数据回显 ArticleCategory.vue

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

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

<script setup>// 六 6.2 element-plus封装的组件
import { ElMessage } from 'element-plus';
// 六 1.2 articleCategoryListService声明一个异步函数  6.2articleCategoryAddService
import {articleCategoryListService,articleCategoryAddService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();//  六  6.1  控制添加分类弹窗
const dialogVisible = ref(false)//六  6.1 添加分类数据模型
const categoryModel = ref({categoryName: '',categoryAlias: ''
})
//六  6.1   添加分类表单校验
const rules = {categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' },],categoryAlias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },]
}
// 六 6.1 调用接口添加表单
const addCategory = async ()=>{let result = await articleCategoryAddService(categoryModel.value);ElMessage.success(result.message? result.message:'添加成功')//隐藏弹窗dialogVisible.value = false//再次访问后台接口,查询所有分类articleCategoryList()
}// 六  7.1.1 弹窗标题
const title=ref('')// 六 7.2展示编辑弹窗
const showDialog = (row)=>{title.value = '修改分类';dialogVisible.value = true//数据拷贝categoryModel.value.categoryName = row.categoryName;categoryModel.value.categoryAlias = row.categoryAlias;//扩展id属性,将来需要传参给后台完成分类的修改categoryModel.value.id = row.id
}</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>  <!--  六 6.1  @click="dialogVisible = true"绑定点击事件   六7.1.3 title='添加分类';--> </div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="创建时间" prop="createTime"></el-table-column><el-table-column label="更新时间" prop="updateTime"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>  <!--  六 7.1.4 @click="title='修改分类';dialogVisible=true"绑定事件   六 7.2 showDialog(row)--><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!--  六  6.1 添加分类弹窗 --><el-dialog v-model="dialogVisible" :title="title" width="30%">   <!--  六 7.1.2 :title="title"绑定标题--><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" @click="addCategory"> 确认 </el-button> <!--  六 6.1 @click="addCategory"绑定点击事件--></span></template></el-dialog></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

7.3 修改文章分类接口调用

7.3.1 article.js中提供修改分类的函数
//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}
7.3.2 修改确定按钮的绑定事件 ArticleCategory.vue
 <span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button></span>
7.3.3 调用接口完成修改的函数  ArticleCategory.vue
<script setup>// 六 6.2 element-plus封装的组件
import { ElMessage } from 'element-plus';
// 六 1.2 articleCategoryListService声明一个异步函数  6.2articleCategoryAddService  7.3.3 articleCategoryUpdateService
import {articleCategoryListService,articleCategoryAddService,articleCategoryUpdateService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();//  六  6.1  控制添加分类弹窗
const dialogVisible = ref(false)//六  6.1 添加分类数据模型
const categoryModel = ref({categoryName: '',categoryAlias: ''
})
//六  6.1   添加分类表单校验
const rules = {categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' },],categoryAlias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },]
}
// 六 6.1 调用接口添加表单
const addCategory = async ()=>{let result = await articleCategoryAddService(categoryModel.value);ElMessage.success(result.message? result.message:'添加成功')//隐藏弹窗dialogVisible.value = false//再次访问后台接口,查询所有分类articleCategoryList()
}// 六  7.1.1 弹窗标题
const title=ref('')// 六 7.2展示编辑弹窗
const showDialog = (row)=>{title.value = '修改分类';dialogVisible.value = true//数据拷贝categoryModel.value.categoryName = row.categoryName;categoryModel.value.categoryAlias = row.categoryAlias;//扩展id属性,将来需要传参给后台完成分类的修改categoryModel.value.id = row.id
}// 六 7.3.3修改分类
const updateCategory=async ()=>{let result = await articleCategoryUpdateService(categoryModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏弹窗dialogVisible.value=false//再次访问后台接口,查询所有分类articleCategoryList()
}</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>  <!--  六 6.1  @click="dialogVisible = true"绑定点击事件   六7.1.3 title='添加分类';--> </div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="创建时间" prop="createTime"></el-table-column><el-table-column label="更新时间" prop="updateTime"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>  <!--  六 7.1.4 @click="title='修改分类';dialogVisible=true"绑定事件   六 7.2 showDialog(row)--><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!--  六  6.1 添加分类弹窗 --><el-dialog v-model="dialogVisible" :title="title" width="30%">   <!--  六 7.1.2 :title="title"绑定标题--><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" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button> <!--  六 6.1 @click="addCategory"绑定点击事件  六 7.3.1 @click="title==='添加分类'? addCategory():updateCategory()"--></span></template></el-dialog></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>
7.3.4 优化添加时数据回显

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

//清空模型数据
const clearCategoryModel = ()=>{categoryModel.value.categoryName='',categoryModel.value.categoryAlias=''
}
<script setup>// 六 6.2 element-plus封装的组件
import { ElMessage } from 'element-plus';
// 六 1.2 articleCategoryListService声明一个异步函数  6.2articleCategoryAddService  7.3.3 articleCategoryUpdateService
import {articleCategoryListService,articleCategoryAddService,articleCategoryUpdateService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();//  六  6.1  控制添加分类弹窗
const dialogVisible = ref(false)//六  6.1 添加分类数据模型
const categoryModel = ref({categoryName: '',categoryAlias: ''
})
//六  6.1   添加分类表单校验
const rules = {categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' },],categoryAlias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },]
}
// 六 6.1 调用接口添加表单
const addCategory = async ()=>{let result = await articleCategoryAddService(categoryModel.value);ElMessage.success(result.message? result.message:'添加成功')//隐藏弹窗dialogVisible.value = false//再次访问后台接口,查询所有分类articleCategoryList()
}// 六  7.1.1 弹窗标题
const title=ref('')// 六 7.2展示编辑弹窗
const showDialog = (row)=>{title.value = '修改分类';dialogVisible.value = true//数据拷贝categoryModel.value.categoryName = row.categoryName;categoryModel.value.categoryAlias = row.categoryAlias;//扩展id属性,将来需要传参给后台完成分类的修改categoryModel.value.id = row.id
}// 六 7.3.3修改分类
const updateCategory=async ()=>{let result = await articleCategoryUpdateService(categoryModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏弹窗dialogVisible.value=false//再次访问后台接口,查询所有分类articleCategoryList()
}//六 7.3.4 清空模型数据
const clearCategoryModel = ()=>{categoryModel.value.categoryName='',categoryModel.value.categoryAlias=''
}</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary" @click="title='添加分类';dialogVisible = true;clearCategoryModel()">添加分类</el-button>  <!--  六 6.1  @click="dialogVisible = true"绑定点击事件   六7.1.3 title='添加分类'; 六 7.3.4 清空模型数据 clearCategoryModel()--> </div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="创建时间" prop="createTime"></el-table-column><el-table-column label="更新时间" prop="updateTime"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>  <!--  六 7.1.4 @click="title='修改分类';dialogVisible=true"绑定事件   六 7.2 showDialog(row)--><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!--  六  6.1 添加分类弹窗 --><el-dialog v-model="dialogVisible" :title="title" width="30%">   <!--  六 7.1.2 :title="title"绑定标题--><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" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button> <!--  六 6.1 @click="addCategory"绑定点击事件  六 7.3.1 @click="title==='添加分类'? addCategory():updateCategory()"--></span></template></el-dialog></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

8、删除文章分类

 8.1 删除接口

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}//六 8.1  删除分类
export const articleCategoryDeleteService = (id) => {return request.delete('/category?id='+id)
}

8.2 删除确认框

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

 8.3 为删除按钮绑定事件

<template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>  <!--  六 7.1.4 @click="title='修改分类';dialogVisible=true"绑定事件   六 7.2 showDialog(row)--><el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button> <!--  六 8.3 @click="deleteCategory(row)" 绑定删除事件--></template>

8.4 调用删除接口

<script setup>// 六 6.2  ElMessage  element-plus封装的组件 六 8.2 ElMessageBox 
import { ElMessage,ElMessageBox } from 'element-plus';
// 六 1.2 articleCategoryListService声明一个异步函数  6.2articleCategoryAddService  7.3.3 articleCategoryUpdateService 8.2 articleCategoryDeleteService
import {articleCategoryListService,articleCategoryAddService,articleCategoryUpdateService,articleCategoryDeleteService} from '@/api/article.js'import {Edit,Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = 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"}
])// 六 1.2 声明一个异步函数
const articleCategoryList = async()=>{let result = await articleCategoryListService()categorys.value = result.data;}
articleCategoryList();//  六  6.1  控制添加分类弹窗
const dialogVisible = ref(false)//六  6.1 添加分类数据模型
const categoryModel = ref({categoryName: '',categoryAlias: ''
})
//六  6.1   添加分类表单校验
const rules = {categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' },],categoryAlias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },]
}
// 六 6.1 调用接口添加表单
const addCategory = async ()=>{let result = await articleCategoryAddService(categoryModel.value);ElMessage.success(result.message? result.message:'添加成功')//隐藏弹窗dialogVisible.value = false//再次访问后台接口,查询所有分类articleCategoryList()
}// 六  7.1.1 弹窗标题
const title=ref('')// 六 7.2展示编辑弹窗
const showDialog = (row)=>{title.value = '修改分类';dialogVisible.value = true//数据拷贝categoryModel.value.categoryName = row.categoryName;categoryModel.value.categoryAlias = row.categoryAlias;//扩展id属性,将来需要传参给后台完成分类的修改categoryModel.value.id = row.id
}// 六 7.3.3修改分类
const updateCategory=async ()=>{let result = await articleCategoryUpdateService(categoryModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏弹窗dialogVisible.value=false//再次访问后台接口,查询所有分类articleCategoryList()
}//六 7.3.4 清空模型数据
const clearCategoryModel = ()=>{categoryModel.value.categoryName='',categoryModel.value.categoryAlias=''
}// 六 8.2 删除分类  给删除按钮绑定事件
const deleteCategory = (row) => {// 提示用户 确认框ElMessageBox.confirm('你确认删除该分类信息吗?','温馨提示',{confirmButtonText: '确认',cancelButtonText: '取消',type: 'warning',}).then(async() => {//用户点击了确认let result = await articleCategoryDeleteService(row.id)// 给出提示ElMessage({type: 'success',message: '删除成功',})//刷新列表//再次访问后台接口,查询所有分类articleCategoryList()}).catch(() => {//用户点击了取消ElMessage({type: 'info',message: '取消删除',})})
}</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章分类</span><div class="extra"><el-button type="primary" @click="title='添加分类';dialogVisible = true;clearCategoryModel()">添加分类</el-button>  <!--  六 6.1  @click="dialogVisible = true"绑定点击事件   六7.1.3 title='添加分类'; 六 7.3.4 清空模型数据 clearCategoryModel()--> </div></div></template><el-table :data="categorys" style="width: 100%"><el-table-column label="序号" width="100" type="index"> </el-table-column><el-table-column label="分类名称" prop="categoryName"></el-table-column><el-table-column label="分类别名" prop="categoryAlias"></el-table-column><el-table-column label="创建时间" prop="createTime"></el-table-column><el-table-column label="更新时间" prop="updateTime"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>  <!--  六 7.1.4 @click="title='修改分类';dialogVisible=true"绑定事件   六 7.2 showDialog(row)--><el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button> <!--  六 8.3 @click="deleteCategory(row)" 绑定删除事件--></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!--  六  6.1 添加分类弹窗 --><el-dialog v-model="dialogVisible" :title="title" width="30%">   <!--  六 7.1.2 :title="title"绑定标题--><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" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button> <!--  六 6.1 @click="addCategory"绑定点击事件  六 7.3.1 @click="title==='添加分类'? addCategory():updateCategory()"--></span></template></el-dialog></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

8.5 测试验证

七、文章列表 

1、 文章列表查询

 1.1 文章列表页面组件ArticleManage.vue

<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = size
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = num
}
</script>
<template><el-card class="page-container"><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary">添加文章</el-button></div></div></template><!-- 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary">搜索</el-button><el-button>重置</el-button></el-form-item></el-form><!-- 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryId"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 分页条 --><el-pagination v-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>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

1.2 解决分页控件英文显示问题main.js

import './assets/main.scss'  // 本项目使用sassimport { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 七 1.2 解决分页控件显示英文问题
import locale from  'element-plus/dist/locale/zh-cn.js'
//五 2.3 
import router from '@/router'import App from './App.vue'
//六 2.2
import {createPinia} from 'pinia'// 六  4.2 导入持久化插件
import {createPersistedState} from 'pinia-persistedstate-plugin'const app = createApp(App)
//六 2.2
const pinia = createPinia();
// 六  4.2
const persist = createPersistedState()
//六  4.2 pinia使用持久化插件
pinia.use(persist)
app.use(pinia)
//五 2.3 
app.use(router)
// 七 1.2 解决分页控件显示英文问题
app.use(ElementPlus,{locale})
app.mount('#app')

1.3 文章分类数据回显ArticleMange.vue  

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

1.4 文章列表接口调用

1.4.1 article.js中提供获取文章列表数据的函数
//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}//六 8.1  删除分类
export const articleCategoryDeleteService = (id) => {return request.delete('/category?id='+id)
}// 八 1.4.1 文章列表查询
export const articleListService = (params) => {return request.get('/article', { params: params })}
1.4.2 ArticleManage.vue中,调用接口获取数据
<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 八 1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   八 1.4.2 articleListService
import { articleCategoryListService ,articleListService} from '@/api/article.js'//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = size
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = num
}// 八 1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 八 1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}// 八  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 八 1.4.2  获取文章列表数据
articleList();</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary">添加文章</el-button></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary">搜索</el-button><el-button>重置</el-button></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

 

1.4.3 当分页条的当前页和每页条数发生变化,重新发送请求获取数据
//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}

2 、搜索和重置

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

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

<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   八 1.4.2 articleListService
import { articleCategoryListService ,articleListService} from '@/api/article.js'//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary">添加文章</el-button></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
</style>

3、添加文章

3.1 添加文章抽屉组件

import {Plus} from '@element-plus/icons-vue'
//控制抽屉是否显示
const visibleDrawer = ref(false)
//添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',sta
<!-- 抽屉 --><el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor">富文本编辑器</div></el-form-item><el-form-item><el-button type="primary">发布</el-button><el-button type="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;}
}

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

<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   八 1.4.2 articleListService
import { articleCategoryListService ,articleListService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'
// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件--></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor">富文本编辑器</div></el-form-item><el-form-item><el-button type="primary">发布</el-button><el-button type="info">草稿</el-button></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
</style>

3.3 富文本编辑器

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

官网地址: VueQuill | Rich Text Editor Component for Vue 3

3.3.1 安装:
npm install @vueup/vue-quill@latest --save

3.3.2 导入组件和样式:
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
3.3.3 页面使用quill组件:
<quill-editortheme="snow"v-model:content="articleModel.content"contentType="html">
</quill-editor>
​
3.3.4 样式美化:
.editor {width: 100%;:deep(.ql-editor) {min-height: 200px;}
}
<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   八 1.4.2 articleListService
import { articleCategoryListService ,articleListService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件--></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-button type="primary">发布</el-button><el-button type="info">草稿</el-button></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
// 七 3.3.4 样式
.editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
}
</style>

3.4 文章封面图片上传

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

auto-upload:是否自动上传

action: 服务器接口路径

name: 上传的文件字段名

headers: 设置上传的请求头

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

</el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="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'
    const tokenStore = useTokenStore();
    <script setup>
    import {Edit,Delete
    } from '@element-plus/icons-vue'import { ref } from 'vue'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   八 1.4.2 articleListService
    import { articleCategoryListService ,articleListService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
    import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
    import { QuillEditor } from '@vueup/vue-quill'
    import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.4 文章封面上传 导入token
    import { useTokenStore } from '@/stores/token.js'
    const tokenStore = useTokenStore();
    // 七 3.4 上传图片成功回调
    const uploadSuccess = (img) => {//img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data
    }// 七 3.1 控制抽屉是否显示
    const visibleDrawer = ref(false)
    // 七 3.1 添加表单数据模型
    const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
    })//文章分类数据模型
    const categorys = 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"}
    ])//用户搜索时选中的分类id
    const categoryId=ref('')//用户搜索时选中的发布状态
    const state=ref('')//文章列表数据模型
    const articles = 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"},
    ])//分页条数据模型
    const pageNum = ref(1)//当前页
    const total = ref(20)//总条数
    const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
    const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
    }
    //当前页码发生变化,调用此函数
    const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
    }// 七  1.3 搜索查询条件 文章列表查询
    const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
    }// 七  1.4.2  获取文章列表数据
    const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
    }//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
    getArticleCategoryList();
    // 七  1.4.2  获取文章列表数据
    articleList();</script>
    <template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件--></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-button type="primary">发布</el-button><el-button type="info">草稿</el-button></el-form-item></el-form></el-drawer></el-card>
    </template>
    <style lang="scss" scoped>
    .page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
    }/* 七 3.1 抽屉样式 */
    .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;}
    }
    // 七 3.3.4 样式
    .editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
    }
    </style>

3.5 添加文章接口调用

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

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}//六 8.1  删除分类
export const articleCategoryDeleteService = (id) => {return request.delete('/category?id='+id)
}// 七 1.4.1 文章列表查询
export const articleListService = (params) => {return request.get('/article', { params: params })}//七 3.5.1添加文章
export const articleAddService = (articleModel)=>{return request.post('/article',articleModel)
}
3.5.2 为已发布和草稿按钮绑定事件
<el-form-item><el-button type="primary" @click="addArticle('已发布')">发布</el-button><el-button type="info" @click="addArticle('草稿')">草稿</el-button>
</el-form-item>
3.5.3 ArticleManage.vue中提供addArticle函数完成添加文章接口的调用
const addArticle=async (state)=>{articleModel.value.state = statelet result = await articleAddService(articleModel.value);ElMessage.success(result.message? result.message:'添加成功')//再次调用getArticles,获取文章articleList()//隐藏抽屉visibleDrawer.value=false
}
<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七 3.5.3 
import { ElMessage } from 'element-plus'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   七 1.4.2 articleListService  七3.5.1  articleAddService
import { articleCategoryListService ,articleListService,articleAddService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.4 文章封面上传 导入token
import { useTokenStore } from '@/stores/token.js'
const tokenStore = useTokenStore();
// 七 3.4 上传图片成功回调
const uploadSuccess = (img) => {//img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data
}// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();// 七 3.5.3 添加文章
const addArticle=async (state)=>{articleModel.value.state = statelet result = await articleAddService(articleModel.value);ElMessage.success(result.message? result.message:'添加成功')//再次调用getArticles,获取文章articleList()//隐藏抽屉visibleDrawer.value=false
}</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件--></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary"></el-button><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%"><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-form-item><el-button type="primary" @click="addArticle('已发布')">发布</el-button> <!-- 七 3.5.2 @click="addArticle('已发布')"--><el-button type="info" @click="addArticle('草稿')">草稿</el-button> <!-- 七 3.5.2@click="addArticle('草稿')"--></el-form-item></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
// 七 3.3.4 样式
.editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
}
</style>

4、编辑文章

4.1 修改文章抽屉页面

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

4.1.1 抽屉标题显示 定义标题
//抽屉标题
const title=ref('')
4.1.2 在抽屉上绑定标题
 <el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%">
4.1.3 为添加文章按钮绑定事件
 <el-button type="primary" @click="title='添加文章';visibleDrawer = true">添加文章</el-button> 
4.1.4 为修改文章按钮绑定事件
 <el-button :icon="Edit" circle plain type="primary" @click="title='修改文章';visibleDrawer='true'"></el-button> 

4.2 数据回显

当点击修改文章按钮时,需要把当前这一条数据的详细信息显示到修改文章的抽屉上,这个叫回显

4.2.1 通过插槽的方式得到被点击按钮所在行的数据
 <template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDrawer(row)"></el-button> <!--七4.1.4 @click="title='修改文章';visibleDrawer='true'"  七 4.2 showDrawer(row) --><el-button :icon="Delete" circle plain type="danger"></el-button></template>
4.2.2 回显函数
// 七 4.2.2展示抽屉弹窗
const showDrawer = (row)=>{title.value = '修改文章';visibleDrawer.value = true//数据拷贝articleModel.value.title = row.title;articleModel.value.categoryId = row.categoryId;articleModel.value.coverImg = row.coverImg;articleModel.value.content = row.content;//扩展id属性,将来需要传参给后台完成文章的修改articleModel.value.id = row.id
}
<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七 3.5.3 
import { ElMessage } from 'element-plus'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   七 1.4.2 articleListService  七3.5.1  articleAddService
import { articleCategoryListService ,articleListService,articleAddService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.4 文章封面上传 导入token
import { useTokenStore } from '@/stores/token.js'
const tokenStore = useTokenStore();
// 七 3.4 上传图片成功回调
const uploadSuccess = (img) => {//img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data
}// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();// 七 3.5.3 添加文章
const addArticle=async (state)=>{articleModel.value.state = statelet result = await articleAddService(articleModel.value);ElMessage.success(result.message? result.message:'添加成功')//再次调用getArticles,获取文章articleList()//隐藏抽屉visibleDrawer.value=false
}//<!-- 七 4.1 抽屉:title="title" -绑定标题 -->
const title=ref('')// 七 4.2.2展示抽屉弹窗
const showDrawer = (row)=>{title.value = '修改文章';visibleDrawer.value = true//数据拷贝articleModel.value.title = row.title;articleModel.value.categoryId = row.categoryId;articleModel.value.coverImg = row.coverImg;articleModel.value.content = row.content;//扩展id属性,将来需要传参给后台完成文章的修改articleModel.value.id = row.id
}</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="title='添加文章';visibleDrawer = true">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件 七 4.1.3 title='添加文章'; --></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDrawer(row)"></el-button> <!--七4.1.4 @click="title='修改文章';visibleDrawer='true'"  七 4.2.1 showDrawer(row) --><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%"> <!-- 七 4.1 抽屉:title="title" -绑定标题 --><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-form-item><el-button type="primary" @click="addArticle('已发布')">发布</el-button> <!-- 七 3.5.2 @click="addArticle('已发布')"--><el-button type="info" @click="addArticle('草稿')">草稿</el-button> <!-- 七 3.5.2@click="addArticle('草稿')"--></el-form-item></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
// 七 3.3.4 样式
.editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
}
</style>

4.3 修改文章接口调用

4.3.1 article.js中提供修改文章的函数
//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}//六 8.1  删除分类
export const articleCategoryDeleteService = (id) => {return request.delete('/category?id='+id)
}// 七 1.4.1 文章列表查询
export const articleListService = (params) => {return request.get('/article', { params: params })}//七 3.5.1添加文章
export const articleAddService = (articleModel)=>{return request.post('/article',articleModel)
}//七 4.3.1修改文章
export const articleUpdateService = (articleModel)=>{return request.put('/article',articleModel)}
4.3.2 修改发布与草稿按钮的绑定事件
<el-form-item><el-button type="primary" @click="title==='添加文章'? addArticle('已发布'): updateArticle('已发布')">发布</el-button> <!-- 七 3.5.2 @click="addArticle('已发布')  七 4.3.2 ;title==='添加文章'? addArticle('已发布'):updateArticle('已发布')"--><el-button type="info" @click="title==='添加文章'? addArticle('草稿'): updateArticle('草稿')">草稿</el-button> <!-- 七 3.5.2@click="addArticle('草稿')"  七 4.3.2 ;title==='添加文章'? addArticle('草稿'):updateArticle('草稿')--></el-form-item>
4.3.3 调用接口完成修改的函数
//七 4.4.4 修改文章
const updateArticle=async ()=>{let result = await articleUpdateService(articleModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏抽屉visibleDrawer.value=false//再次访问后台接口,查询所有文章articleList()
}

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

//七 4.4.3 清空模型数据
const clearArticleModel = ()=>{articleModel.value.title='',articleModel.value.categoryId='',articleModel.value.coverImg='',articleModel.value.content='<br>',articleModel.value.state=''
}

修改 添加文章按钮的点击事件()

<el-button type="primary" @click="title='添加文章';visibleDrawer = true;clearArticleModel()">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件 七 4.1.3 title='添加文章';  七 4.3.3 ;clearArticleModel() -->
<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七 3.5.3 
import { ElMessage } from 'element-plus'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   七 1.4.2 articleListService  七3.5.1  articleAddService 七 4.3.3articleUpdateService 
import { articleCategoryListService ,articleListService,articleAddService,articleUpdateService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.4 文章封面上传 导入token
import { useTokenStore } from '@/stores/token.js'
const tokenStore = useTokenStore();
// 七 3.4 上传图片成功回调
const uploadSuccess = (img) => {//img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data
}// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();// 七 3.5.3 添加文章
const addArticle=async (state)=>{articleModel.value.state = statelet result = await articleAddService(articleModel.value);ElMessage.success(result.message? result.message:'添加成功')//再次调用getArticles,获取文章articleList()//隐藏抽屉visibleDrawer.value=false
}//<!-- 七 4.1 抽屉:title="title" -绑定标题 -->
const title=ref('')// 七 4.2.2展示抽屉弹窗
const showDrawer = (row)=>{title.value = '修改文章';visibleDrawer.value = true//数据拷贝articleModel.value.title = row.title;articleModel.value.categoryId = row.categoryId;articleModel.value.coverImg = row.coverImg;articleModel.value.content = row.content;//扩展id属性,将来需要传参给后台完成文章的修改articleModel.value.id = row.id
}//七 4.4.3 修改文章
const updateArticle=async ()=>{let result = await articleUpdateService(articleModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏抽屉visibleDrawer.value=false//再次访问后台接口,查询所有文章articleList()
}//七 4.4.3 清空模型数据
const clearArticleModel = ()=>{articleModel.value.title='',articleModel.value.categoryId='',articleModel.value.coverImg='',articleModel.value.content=' ', // 这里清空不了富文本,没有找到解决方案 暂时多了一个空格处理articleModel.value.state=''
}</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="title='添加文章';visibleDrawer = true;clearArticleModel()">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件 七 4.1.3 title='添加文章';  七 4.3.3 ;clearArticleModel() --></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDrawer(row)"></el-button> <!--七4.1.4 @click="title='修改文章';visibleDrawer='true'"  七 4.2.1 showDrawer(row) --><el-button :icon="Delete" circle plain type="danger"></el-button></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%"> <!-- 七 4.1 抽屉:title="title" -绑定标题 --><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-form-item><el-button type="primary" @click="title==='添加文章'? addArticle('已发布'): updateArticle('已发布')">发布</el-button> <!-- 七 3.5.2 @click="addArticle('已发布')  七 4.3.2 ;title==='添加文章'? addArticle('已发布'):updateArticle('已发布')"--><el-button type="info" @click="title==='添加文章'? addArticle('草稿'): updateArticle('草稿')">草稿</el-button> <!-- 七 3.5.2@click="addArticle('草稿')"  七 4.3.2 ;title==='添加文章'? addArticle('草稿'):updateArticle('草稿')--></el-form-item></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
// 七 3.3.4 样式
.editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
}
</style>

5、删除文章

5.1 接口调用

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

//导入request.js请求工具
import request from '@/utils/request.js'
// 六 2.4 导入@/stores/token.js
import { useTokenStore } from '@/stores/token.js'// 六  文章分类列表查询函数  
export const articleCategoryListService = (registerData) =>{    // 六 2.4获取token状态// 六 3.2// const tokenStore = useTokenStore()//通过请求头Authorization携带token// return request.get('/category', { headers: { 'Authorization': tokenStore.token } });return request.get('/category');}// 六 6.2  文章分类 添加文章分类 函数  json 参数
export const articleCategoryAddService = (categoryModel) => {return request.post('/category', categoryModel)
}//六 7.3.1   修改分类
export const articleCategoryUpdateService = (categoryModel)=>{return request.put('/category',categoryModel)}//六 8.1  删除分类
export const articleCategoryDeleteService = (id) => {return request.delete('/category?id='+id)
}// 七 1.4.1 文章列表查询
export const articleListService = (params) => {return request.get('/article', { params: params })}//七 3.5.1添加文章
export const articleAddService = (articleModel)=>{return request.post('/article',articleModel)
}//七 4.3.1修改文章
export const articleUpdateService = (articleModel)=>{return request.put('/article',articleModel)}//七 5.1删除文章
export const articleDeleteService = (id) => {return request.delete('/article?id='+id)}

5.2 为删除按钮绑定事件

<template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDrawer(row)"></el-button> <!--七4.1.4 @click="title='修改文章';visibleDrawer='true'"  七 4.2.1 showDrawer(row) --><el-button :icon="Delete" circle plain type="danger" @click="deleteArticle(row)"></el-button> <!-- 七 5.2 为删除按钮绑定事件--></template>

5.3 添加删除弹窗,当用户点击确认后,调用接口删除分类

<script setup>
import {Edit,Delete
} from '@element-plus/icons-vue'import { ref } from 'vue'// 七 3.5.3 
import { ElMessage,ElMessageBox } from 'element-plus'// 七  1.3 articleCategoryListService搜索查询条件 文章列表查询 导入   七 1.4.2 articleListService  七3.5.1  articleAddService 七 4.3.3articleUpdateService  七 5.3 articleDeleteService
import { articleCategoryListService ,articleListService,articleAddService,articleUpdateService,articleDeleteService} from '@/api/article.js'// 七 3.1 导入添加抽屉组件
import {Plus} from '@element-plus/icons-vue'// 七 3.3.2 导入组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'// 七 3.4 文章封面上传 导入token
import { useTokenStore } from '@/stores/token.js'
const tokenStore = useTokenStore();
// 七 3.4 上传图片成功回调
const uploadSuccess = (img) => {//img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}articleModel.value.coverImg=img.data
}// 七 3.1 控制抽屉是否显示
const visibleDrawer = ref(false)
// 七 3.1 添加表单数据模型
const articleModel = ref({title: '',categoryId: '',coverImg: '',content:'',state:''
})//文章分类数据模型
const categorys = 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"}
])//用户搜索时选中的分类id
const categoryId=ref('')//用户搜索时选中的发布状态
const state=ref('')//文章列表数据模型
const articles = 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"},
])//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {pageSize.value = sizearticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {pageNum.value = numarticleList()  //七  1.4.3 **当分页条的当前页和每页条数发生变化,重新发送请求获取数据**
}// 七  1.3 搜索查询条件 文章列表查询
const getArticleCategoryList = async () => {//获取所有分类let resultC = await articleCategoryListService();categorys.value = resultC.data
}// 七  1.4.2  获取文章列表数据
const articleList = async () => {let params = {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result = await articleListService(params);//渲染视图total.value = result.data.total;articles.value = result.data.items;//处理数据,给数据模型扩展一个属性categoryName,分类名称for (let i = 0; i < articles.value.length; i++) {let article = articles.value[i];for (let j = 0; j < categorys.value.length; j++) {if (article.categoryId == categorys.value[j].id) {article.categoryName = categorys.value[j].categoryName;}}}
}//七  1.3 搜索查询条件 文章列表查询 调用函数getArticleCategoryList
getArticleCategoryList();
// 七  1.4.2  获取文章列表数据
articleList();// 七 3.5.3 添加文章
const addArticle=async (state)=>{articleModel.value.state = statelet result = await articleAddService(articleModel.value);ElMessage.success(result.message? result.message:'添加成功')//再次调用getArticles,获取文章articleList()//隐藏抽屉visibleDrawer.value=false
}//<!-- 七 4.1 抽屉:title="title" -绑定标题 -->
const title=ref('')// 七 4.2.2展示抽屉弹窗
const showDrawer = (row)=>{title.value = '修改文章';visibleDrawer.value = true//数据拷贝articleModel.value.title = row.title;articleModel.value.categoryId = row.categoryId;articleModel.value.coverImg = row.coverImg;articleModel.value.content = row.content;//扩展id属性,将来需要传参给后台完成文章的修改articleModel.value.id = row.id
}//七 4.4.3 修改文章
const updateArticle=async (state)=>{articleModel.value.state = statelet result = await articleUpdateService(articleModel.value)ElMessage.success(result.message? result.message:'修改成功')//隐藏抽屉visibleDrawer.value=false//再次访问后台接口,查询所有文章articleList()
}//七 4.4.3 清空模型数据
const clearArticleModel = ()=>{articleModel.value.title='',articleModel.value.categoryId='',articleModel.value.coverImg='',articleModel.value.content=' ', // 这里清空不了富文本,没有找到解决方案 暂时多了一个空格处理articleModel.value.state=''
}// 七 5.3 删除文章(记得引用ElMessageBox)  给删除按钮绑定事件
const deleteArticle = (row) => {// 提示用户 确认框ElMessageBox.confirm('你确认删除该文章信息吗?','温馨提示',{confirmButtonText: '确认',cancelButtonText: '取消',type: 'warning',}).then(async() => {//用户点击了确认let result = await articleDeleteService(row.id)// 给出提示ElMessage({type: 'success',message: '删除成功',})//刷新列表//再次访问后台接口,查询所有文章articleList()}).catch(() => {//用户点击了取消ElMessage({type: 'info',message: '取消删除',})})
}</script>
<template><el-card class="page-container"><!-- 1 标题 --><template #header><div class="header"><span>文章管理</span><div class="extra"><el-button type="primary" @click="title='添加文章';visibleDrawer = true;clearArticleModel()">添加文章</el-button>  <!-- 七 3.2 @click="visibleDrawer = true" 绑定查询事件 七 4.1.3 title='添加文章';  七 4.3.3 ;clearArticleModel() --></div></div></template><!-- 2 搜索表单 --><el-form inline><el-form-item label="文章分类:"><el-select placeholder="请选择" v-model="categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName":value="c.id"></el-option></el-select></el-form-item><el-form-item label="发布状态:"><el-select placeholder="请选择" v-model="state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="articleList">搜索</el-button>  <!-- 七 2  绑定查询事件--><el-button @click="categoryId='';state='';articleList()">重置</el-button>  <!-- 七 2  绑定重置事件--></el-form-item></el-form><!-- 3 文章列表 --><el-table :data="articles" style="width: 100%"><el-table-column label="文章标题" width="400" prop="title"></el-table-column><el-table-column label="分类" prop="categoryName"></el-table-column><el-table-column label="发表时间" prop="createTime"> </el-table-column><el-table-column label="更新时间" prop="updateTime"> </el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :icon="Edit" circle plain type="primary" @click="showDrawer(row)"></el-button> <!--七4.1.4 @click="title='修改文章';visibleDrawer='true'"  七 4.2.1 showDrawer(row) --><el-button :icon="Delete" circle plain type="danger" @click="deleteArticle(row)"></el-button> <!-- 七 5.2 为删除按钮绑定事件--></template></el-table-column><template #empty><el-empty description="没有数据" /></template></el-table><!-- 4 分页条 --><el-pagination v-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" /><!-- 七 3.1 抽屉 --><el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%"> <!-- 七 4.1 抽屉:title="title" -绑定标题 --><!-- 添加文章表单 --><el-form :model="articleModel" label-width="100px" ><el-form-item label="文章标题" ><el-input v-model="articleModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类"><el-select placeholder="请选择" v-model="articleModel.categoryId"><el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id"></el-option></el-select></el-form-item><el-form-item label="文章封面"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数 --><el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item><el-form-item label="文章内容"><div class="editor"><quill-editor theme="snow" v-model:content="articleModel.content" contentType="html"> <!-- 七 3.3.3页面使用quill组件 --></quill-editor></div></el-form-item><el-form-item><el-form-item><el-button type="primary" @click="title==='添加文章'? addArticle('已发布'): updateArticle('已发布')">发布</el-button> <!-- 七 3.5.2 @click="addArticle('已发布')  七 4.3.2 ;title==='添加文章'? addArticle('已发布'):updateArticle('已发布')"--><el-button type="info" @click="title==='添加文章'? addArticle('草稿'): updateArticle('草稿')">草稿</el-button> <!-- 七 3.5.2@click="addArticle('草稿')"  七 4.3.2 ;title==='添加文章'? addArticle('草稿'):updateArticle('草稿')--></el-form-item></el-form-item></el-form></el-drawer></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}/* 七 3.1 抽屉样式 */
.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;}
}
// 七 3.3.4 样式
.editor {width: 100%;  :deep(.ql-editor) {min-height: 200px;}
}
</style>

八、顶部导航栏信息展示

在Layout.vue中,页面加载完就发送请求,获取个人信息展示,并存储到pinia中,因为将来在个人中心中修改信息的时候还需要使用

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

//导入request.js请求工具
import request from '@/utils/request.js'// 二  注册函数  提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}// 三  登录函数 提供调用登录接口的函数
export const userLoginService = (loginData)=>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in loginData){params.append(key,loginData[key]);}return request.post('/user/login',params);}// 八  1获取个人信息
export const userInfoGetService = ()=>{return request.get('/user/userInfo');}

2、 src/stores/userInfo.js中,定义个人中心状态

import {defineStore} from 'pinia'
import {ref} from 'vue'
const useUserInfoStore = defineStore('userInfo',()=>{//定义状态相关的内容const info = ref({})const setInfo = (newInfo)=>{info.value = newInfo}const removeInfo = ()=>{info.value = {}}return {info,setInfo,removeInfo}},{persist:true})export default useUserInfoStore;

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

import {ref} from 'vue'
//导入接口函数
import { userInfoGetService } from '@/api/user.js'
//导入pinia
import useUserInfoStore  from '@/stores/userInfo.js'const userInfoStore = useUserInfoStore();
//获取个人信息
const getUserInfo = async ()=>{let result = await userInfoGetService();//存储piniauserInfoStore.info =result.data;
}
getUserInfo()

4、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" />
​全部Layout.vue
<script setup>
import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
// ======= 八  3  个人用户信息
import {ref} from 'vue'
//导入接口函数
import { userInfoGetService } from '@/api/user.js'
//导入pinia
import useUserInfoStore  from '@/stores/userInfo.js'const userInfoStore = useUserInfoStore();
//获取个人信息
const getUserInfo = async ()=>{let result = await userInfoGetService();//存储piniauserInfoStore.info =result.data;
}
getUserInfo()</script><template><!-- element-plus 中的容器 --><el-container class="layout-container"><!-- 左侧菜单 --><el-aside width="200px"><div class="el-aside__logo"></div><!-- element-plus 菜单标签 --><el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"router><el-menu-item index="/article/category">  <!-- 五 3.3  --><el-icon><Management /></el-icon><span>文章分类</span></el-menu-item><el-menu-item index="/article/manage">  <!-- 五 3.3  --><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-item index="/user/info">  <!-- 五 3.3  --><el-icon><User /></el-icon><span>基本资料</span></el-menu-item><el-menu-item index="/user/avatar">  <!-- 五 3.3  --><el-icon><Crop /></el-icon><span>更换头像</span></el-menu-item><el-menu-item index="/user/resetPassword">  <!-- 五 3.3  --><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 ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div> <!-- 八 4 {{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}--><el-dropdown placement="bottom-end"><span class="el-dropdown__box"><el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" /> <!-- 八 4 userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar--><el-icon><CaretBottom /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><!-- 中间区域 --><el-main><!-- 五 3.3  --><!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">内容展示区</div> --><router-view></router-view></el-main><!-- 底部区域 --><el-footer>国际大事件 ©2023 Created by 春天的菠菜</el-footer></el-container></el-container>
</template><style lang="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>

九 、页面右上角下拉菜单el-dropdown中功能实现

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

  • 基本资料

  • 更换头像

  • 重置密码

  • 退出登录

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

1、路由实现:

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

<!--  下拉菜单--><!--  command: 条目被点击后会触发,在事件函数上可以声明一个参数,接收条目对应的指令 --><el-dropdown placement="bottom-end" @command="handleCommand"><span class="el-dropdown__box"><el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" /> <!-- 八 4 userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar--><el-icon><CaretBottom /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="resetPassword" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown>

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

<el-dropdown placement="bottom-end" @command="handleCommand">

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

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

2、 退出登录实现:

<script setup>
import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
//  九 1
//dropDown条目被点击后,回调的函数
import {useRouter} from 'vue-router'//九 2
import {ElMessage,ElMessageBox} from 'element-plus'
import { useTokenStore } from '@/stores/token.js'// ======= 八  3  个人用户信息
import {ref} from 'vue'
//导入接口函数
import { userInfoGetService } from '@/api/user.js'
//导入pinia
import useUserInfoStore  from '@/stores/userInfo.js'const userInfoStore = useUserInfoStore();
//获取个人信息
const getUserInfo = async ()=>{let result = await userInfoGetService();//存储piniauserInfoStore.info =result.data;
}
getUserInfo()
//九 2const tokenStore = useTokenStore()
//九 1
const router = useRouter()
const handleCommand = (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><!-- element-plus 中的容器 --><el-container class="layout-container"><!-- 左侧菜单 --><el-aside width="200px"><div class="el-aside__logo"></div><!-- element-plus 菜单标签 --><el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"router><el-menu-item index="/article/category">  <!-- 五 3.3  --><el-icon><Management /></el-icon><span>文章分类</span></el-menu-item><el-menu-item index="/article/manage">  <!-- 五 3.3  --><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-item index="/user/info">  <!-- 五 3.3  --><el-icon><User /></el-icon><span>基本资料</span></el-menu-item><el-menu-item index="/user/avatar">  <!-- 五 3.3  --><el-icon><Crop /></el-icon><span>更换头像</span></el-menu-item><el-menu-item index="/user/resetPassword">  <!-- 五 3.3  --><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 ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div> <!-- 八 4 {{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}--><!--  下拉菜单--><!--  command: 条目被点击后会触发,在事件函数上可以声明一个参数,接收条目对应的指令 --><el-dropdown placement="bottom-end" @command="handleCommand"><span class="el-dropdown__box"><el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" /> <!-- 八 4 userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar--><el-icon><CaretBottom /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="resetPassword" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><!-- 中间区域 --><el-main><!-- 五 3.3  --><!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">内容展示区</div> --><router-view></router-view></el-main><!-- 底部区域 --><el-footer>国际大事件 ©2023 Created by 春天的菠菜</el-footer></el-container></el-container>
</template><style lang="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>

十、基本资料修改

1、 基本资料页面组件UserInfo.vue

views\user\UserInfo.vue
<script setup>
import { ref } from 'vue'
const userInfo = ref({id: 0,username: 'zhangsan',nickname: 'zs',email: 'zs@163.com',
})
const rules = {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-card class="page-container"><template #header><div class="header"><span>基本资料</span></div></template><el-row><el-col :span="12"><el-form :model="userInfo" :rules="rules" label-width="100px" size="large"><el-form-item label="登录名称"><el-input v-model="userInfo.username" disabled></el-input></el-form-item><el-form-item label="用户昵称" prop="nickname"><el-input v-model="userInfo.nickname"></el-input></el-form-item><el-form-item label="用户邮箱" prop="email"><el-input v-model="userInfo.email"></el-input></el-form-item><el-form-item><el-button type="primary">提交修改</el-button></el-form-item></el-form></el-col></el-row></el-card>
</template>

2、 表单数据回显

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

// 十  2
import  useUserInfoStore  from '@/stores/userInfo.js';
const userInfoStore = useUserInfoStore()
const userInfo = ref({...userInfoStore.info})

3、 接口调用

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

//导入request.js请求工具
import request from '@/utils/request.js'// 二  注册函数  提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}// 三  登录函数 提供调用登录接口的函数
export const userLoginService = (loginData)=>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in loginData){params.append(key,loginData[key]);}return request.post('/user/login',params);}// 八  1获取个人信息
export const userInfoGetService = ()=>{return request.get('/user/userInfo');}// 十 3 修改个人信息
export const userInfoUpdateService = (userInfo)=>{return request.put('/user/update',userInfo)}

为修改按钮绑定单击事件

<el-button type="primary" @click="updateUserInfo">提交修改</el-button> <!-- 十  3  @click="updateUserInfo"-->

提供updateUserInfo函数

const updateUserInfo = async ()=>{let result = await userInfoUpdateService(userInfo.value)ElMessage.success(result.message? result.message:'修改成功')//更新pinia中的数据userInfoStore.info.nickname=userInfo.value.nicknameuserInfoStore.info.email = userInfo.value.email
}
<script setup>
import { ref } from 'vue'
// 十 3
import { ElMessage } from 'element-plus';
import { userInfoUpdateService } from '@/api/user.js';// 十  2
import  useUserInfoStore  from '@/stores/userInfo.js';
const userInfoStore = useUserInfoStore()
const userInfo = ref({...userInfoStore.info})const rules = {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' }]
}// 十  3
const updateUserInfo = async ()=>{let result = await userInfoUpdateService(userInfo.value)ElMessage.success(result.message? result.message:'修改成功')//更新pinia中的数据userInfoStore.info.nickname=userInfo.value.nicknameuserInfoStore.info.email = userInfo.value.email
}
</script>
<template><el-card class="page-container"><template #header><div class="header"><span>基本资料</span></div></template><el-row><el-col :span="12"><el-form :model="userInfo" :rules="rules" label-width="100px" size="large"><el-form-item label="登录名称"><el-input v-model="userInfo.username" disabled></el-input></el-form-item><el-form-item label="用户昵称" prop="nickname"><el-input v-model="userInfo.nickname"></el-input></el-form-item><el-form-item label="用户邮箱" prop="email"><el-input v-model="userInfo.email"></el-input></el-form-item><el-form-item><el-button type="primary" @click="updateUserInfo">提交修改</el-button> <!-- 十  3  @click="updateUserInfo"--></el-form-item></el-form></el-col></el-row></el-card>
</template>

十一、修改头像

1、 修改头像页面组件UserAvatar.vue

<script setup>
import { Plus, Upload } from '@element-plus/icons-vue'
import {ref} from 'vue'
import avatar from '@/assets/default.png'
const uploadRef = ref()//用户头像地址
const imgUrl= avatar</script><template><el-card class="page-container"><template #header><div class="header"><span>更换头像</span></div></template><el-row><el-col :span="12"><el-upload ref="uploadRef"class="avatar-uploader" :show-file-list="false"><img v-if="imgUrl" :src="imgUrl" class="avatar" /><img v-else src="avatar" width="278" /></el-upload><br /><el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">选择图片</el-button><el-button type="success" :icon="Upload" size="large">上传头像</el-button></el-col></el-row></el-card>
</template><style lang="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>

2、 头像回显

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

//读取用户信息
import {ref} from 'vue'
import useUserInfoStore from '@/stores/userInfo.js'const userInfoStore = useUserInfoStore()
//用户头像地址
const imgUrl=ref(userInfoStore.info.userPic)

img标签上绑定图片地址

<img v-if="imgUrl" :src="imgUrl" class="avatar" />
<img v-else :src="avatar" width="278" />

3、 头像上传

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

  • action: 服务器接口路径

  • headers: 设置请求头,需要携带token

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

  • name: 上传图片的字段名称

<el-upload ref="uploadRef"class="avatar-uploader" :show-file-list="false":auto-upload="true"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="imgUrl" :src="imgUrl" class="avatar" /><img v-else src="avatar" width="278" /></el-upload>

提供上传成功的回调函数

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

外部触发图片选择

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

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

4、 上传头像接口调用

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

//导入request.js请求工具
import request from '@/utils/request.js'// 二  注册函数  提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}// 三  登录函数 提供调用登录接口的函数
export const userLoginService = (loginData)=>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in loginData){params.append(key,loginData[key]);}return request.post('/user/login',params);}// 八  1获取个人信息
export const userInfoGetService = ()=>{return request.get('/user/userInfo');}// 十 3 修改个人信息
export const userInfoUpdateService = (userInfo)=>{return request.put('/user/update',userInfo)}// 十  4 修改头像
export const userAvatarUpdateService=(avatarUrl)=>{let params = new URLSearchParams();params.append('avatarUrl',avatarUrl)return request.patch('/user/updateAvatar',params)
}

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

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

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

//调用接口,更新头像url
import {userAvatarUpdateService} from '@/api/user.js'
import {ElMessage} from 'element-plus'
const updateAvatar = async ()=>{let result = await userAvatarUpdateService(imgUrl.value)ElMessage.success(result.message? result.message:'修改成功')//更新pinia中的数据userInfoStore.info.userPic=imgUrl.value
}
<script setup>
import { Plus, Upload } from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
// 十  3
import { useTokenStore } from '@/stores/token.js'
// 十 4调用接口,更新头像url
import {userAvatarUpdateService} from '@/api/user.js'
import {ElMessage} from 'element-plus'
// 十 2 
//读取用户信息
import {ref} from 'vue'
import useUserInfoStore from '@/stores/userInfo.js'const userInfoStore = useUserInfoStore()
//用户头像地址
const imgUrl=ref(userInfoStore.info.userPic)// 十  3
const tokenStore = useTokenStore();
//图片上传成功的回调
const uploadSuccess = (result)=>{//回显图片imgUrl.value = result.data
}// 十 、3//获取el-upload元素
const uploadRef = ref()// 十 4
const updateAvatar = async ()=>{let result = await userAvatarUpdateService(imgUrl.value)ElMessage.success(result.message? result.message:'修改成功')//更新pinia中的数据userInfoStore.info.userPic=imgUrl.value
}
</script><template><el-card class="page-container"><template #header><div class="header"><span>更换头像</span></div></template><el-row><el-col :span="12"><!-- auto-upload:是否自动上传action: 服务器接口路径name: 上传的文件字段名headers: 设置上传的请求头on-success: 上传成功的回调函数                     --><el-upload ref="uploadRef"class="avatar-uploader" :show-file-list="false":auto-upload="true"action="/api/upload"name="file":headers="{Authorization:tokenStore.token}":on-success="uploadSuccess"><img v-if="imgUrl" :src="imgUrl" class="avatar" /><img v-else :src="avatar" width="278" /></el-upload><br /><el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">选择图片</el-button><el-button type="success" :icon="Upload" size="large" @click="updateAvatar"> <!-- 十  4  @click="updateAvatar"-->上传头像</el-button></el-col></el-row></el-card>
</template><style lang="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>

十二、 重置密码

1、user.js 重置密码

//导入request.js请求工具
import request from '@/utils/request.js'// 二  注册函数  提供调用注册接口的函数
export const userRegisterService = (registerData) =>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in registerData){params.append(key,registerData[key]);}return request.post('/user/register',params);}// 三  登录函数 提供调用登录接口的函数
export const userLoginService = (loginData)=>{//借助于URLSearchParam完成参数传递const params = new URLSearchParams();for(let key in loginData){params.append(key,loginData[key]);}return request.post('/user/login',params);}// 八  1获取个人信息
export const userInfoGetService = ()=>{return request.get('/user/userInfo');}// 十 3 修改个人信息
export const userInfoUpdateService = (userInfo)=>{return request.put('/user/update',userInfo)}// 十一  4 修改头像
export const userAvatarUpdateService=(avatarUrl)=>{let params = new URLSearchParams();params.append('avatarUrl',avatarUrl)return request.patch('/user/updateAvatar',params)
}// 十二  重置密码
export const userUpdatePwdService=(userPwd)=>{    return request.patch('/user/updatePwd',userPwd)
}

 2、UserResetPassword.vue

 views/UserResetPassword.vue

<script setup>
import { ElMessage } from 'element-plus';
import { useTokenStore } from '@/stores/token.js'
import { ref } from 'vue'
import { userUpdatePwdService } from '@/api/user.js';
import {useRouter} from 'vue-router'
const router = useRouter()
const userPwd = ref({    old_pwd: '',new_pwd: '',re_pwd: '',
})const tokenStore = useTokenStore()
const updateUserPwd = async ()=>{let result = await userUpdatePwdService(userPwd.value)// ElMessage.success(result.message? result.message:'修改成功')//退出登录//1.清空pinia中存储的token(pinia中个人信息没有存储密码信息,所以不处理)tokenStore.removeToken()//2.跳转到登录页面router.push('/login')ElMessage({type: 'success',message: '修改密码成功,请重新成功',})
}const rules = {old_pwd: [{ required: true, message: '请输入原始密码', trigger: 'blur' },{pattern: /^\S{5,16}$/,message: '原始密码必须是5-16位的非空字符串',trigger: 'blur'}],new_pwd: [{ required: true, message: '请输入新密码', trigger: 'blur' },{pattern: /^\S{5,16}$/,message: '新密码必须是5-16位的非空字符串',trigger: 'blur'}],re_pwd: [{ required: true, message: '请输入确认密码', trigger: 'blur' },{pattern: /^\S{5,16}$/,message: '确认密码必须是5-16位的非空字符串',trigger: 'blur'}]
}
</script>
<template><el-card class="page-container"><template #header><div class="header"><span>重置密码</span></div></template><el-row><el-col :span="12">                <el-form :model="userPwd" :rules="rules" label-width="100px" size="large"><el-form-item label="原密码" prop="old_pwd"><el-input type="password" v-model="userPwd.old_pwd"></el-input></el-form-item><el-form-item label="新密码" prop="new_pwd"><el-input type="password" v-model="userPwd.new_pwd"></el-input></el-form-item><el-form-item label="确认密码" prop="re_pwd"><el-input type="password" v-model="userPwd.re_pwd"></el-input></el-form-item><el-form-item><el-button type="primary" @click="updateUserPwd">提交修改</el-button></el-form-item></el-form></el-col></el-row></el-card>
</template>

相关文章:

【SpringBoot3+Vue3】五【完】【实战篇】-前端(配合后端)

目录 一、环境准备 1、创建Vue工程 2、安装依赖 2.1 安装项目所需要的vue依赖 2.2 安装element-plus依赖 2.2.1 安装 2.2.2 项目导入element-plus 2.3 安装axios依赖 2.4 安装sass依赖 3、目录调整 3.1 删除部分默认目录下文件 3.1.1 src/components下自动生成的…...

[LaTex]arXiv投稿攻略——jpg/png转pdf

一、将图片复制进ppt&#xff0c;右键单击图片选择设置图片格式&#xff0c;获取图片高度和宽度 二、选择“设计-幻灯片大小-自定义幻灯片大小” 三、设置幻灯片大小为图片大小 四、 选择“最大化” 五、 检查幻灯片大小是否与图像大小一致 六、导出为PDF...

使用Pytorch从零开始构建GRU

门控循环单元 (GRU) 是 LSTM 的更新版本。让我们揭开这个网络的面纱并探索这两个兄弟姐妹之间的差异。 您听说过 GRU 吗&#xff1f;门控循环单元&#xff08;GRU&#xff09;是更流行的长短期记忆&#xff08;LSTM&#xff09;网络的弟弟&#xff0c;也是循环神经网络&#x…...

【尚跑】2023宝鸡马拉松安全完赛,顺利PB达成

1、赛事背景 千年宝地&#xff0c;一马当先&#xff01;10月15日7时30分&#xff0c;吉利银河2023宝鸡马拉松在宝鸡市行政中心广场鸣枪开跑。 不可忽视的是&#xff0c;这次赛事的卓越之处不仅在于规模和参与人数&#xff0c;还在于其精心的策划和细致入微的组织。为了确保每位…...

Mac nginx安装,通过源码安装教程

第一部分 安装参考网址&#xff1a; https://blog.csdn.net/a1004084857/article/details/128512612&#xff1b; 以上步骤执行完&#xff0c;进入找到sbin目录&#xff0c;查看下面是不是有nginx可执行文件&#xff0c;如果有在当前sbin下执行./nginx,就会发现NGINX已启动 第…...

TypeScript中的枚举是什么?

在TypeScript中&#xff0c;枚举&#xff08;Enum&#xff09;是一种用于定义一组有命名的常量值的数据类型。它们可以提供更具可读性和可维护性的代码。 枚举的作用是为一组相关的值提供一个易于理解和使用的命名空间。它们可以用于代表一系列可能的选项、状态或标志&#xf…...

进程并发-信号量经典例题-面包师问题

1 题目描述 面包师有很多面包和蛋糕&#xff0c;由N个销售人员销售。每个顾客进店后先取一个号&#xff0c;并且等着叫号。当一个销售人员空闲下来&#xff0c;就叫下一个号。试用信号量的P、V操作设计该问题的同步算法&#xff0c;给出所用共享变量&#xff08;如果需要&…...

c语言练习12周(11~15)

编写double fun(int a[],int n)函数&#xff0c;计算返回评分数组a中&#xff0c;n个评委打分&#xff0c;去掉一个最高分去掉一个最低分之后的平均分 题干编写double fun(int a[],int n)函数&#xff0c;计算返回评分数组a中&#xff0c;n个评委打分&#xff0c;去掉一…...

Java 实现视频转音频功能

在实际开发中,我们经常需要处理各种多媒体文件。本文将介绍如何使用 Java 语言实现将视频文件转换为音频文件的功能。我们将使用 FFmpeg 工具来进行视频转换操作,并通过 Java 的 ProcessBuilder 实现调用系统命令执行 FFmpeg 的功能。 准备工作 首先,我们需要确保系统中已安…...

可以在Playgrounds或Xcode Command Line Tool开始学习Swift

一、用Playgrounds 1. App Store搜索并安装Swift Playgrounds 2. 打开Playgrounds&#xff0c;点击 文件-新建图书。然后就可以编程了&#xff0c;如下&#xff1a; 二、用Xcode 1. 安装Xcode 2. 打开Xcode&#xff0c;选择Creat New Project 3. 选择macOS 4. 选择Comman…...

IDC最新报告,增速减缓+AI增势,阿里云视频云中国市场第一

国际权威数据公司IDC发布 《中国视频云市场跟踪&#xff08;2023 H1&#xff09;》报告 自2018年至今&#xff0c;阿里云持续保持 中国视频云整体市场第一 整体市场占比达24.4% 01 第一之外&#xff0c;低谷之上 近期&#xff0c;国际权威数据公司IDC最新发布了《中国视频…...

常见状态码

欢迎大家到我的博客浏览。常见状态码 | YinKais Blog 常见状态码<!--more--> 1、200 200&#xff1a;服务器已经接收了请求&#xff0c;但处理还没有完成。 204&#xff1a;服务器已经成功处理了请求&#xff0c;但相应中没有任何返回内容。比如 DELETE 请求。 206&…...

Spring原理——基于xml配置文件创建IOC容器的过程

Spring框架的核心之一是IOC&#xff0c;那么我们是怎么创建出来的Bean呢&#xff1f; 作者进行了简单的总结&#xff0c;希望能对你有所帮助。 IOC的创建并不是通过new而是利用了java的反射机制&#xff0c;利用了newInstance方法进行的创建对象。 首先&#xff0c;我们先定义…...

CUDA initialization failure with error: 999

ubuntu20.04&#xff0c;安装tensorRT, 执行example里面的./sample_char_rnn程序&#xff0c;测试时候报了如标题的一个错误&#xff0c;居然如下两行代码这样解决了&#xff0c;这两行命令好像是重新加载nvidia内核模块&#xff0c;有点玄学&#xff1a; sudo rmmod nvidia_u…...

一些权限方面的思考

一些权限方面的思考 背景说明自定义注解解析自定义注解 背景 鉴权可以通过切面做抽取 说明 都是一些伪代码, 不能直接使用, 提供一种思路. 都是一些伪代码, 不能直接使用, 提供一种思路. 都是一些伪代码, 不能直接使用, 提供一种思路. 自定义注解 自定义注解: Permission …...

NX二次开发UF_CURVE_add_faces_ocf_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_add_faces_ocf_data Defined in: uf_curve.h int UF_CURVE_add_faces_ocf_data(tag_t face_tag, UF_CURVE_ocf_data_p_t uf_offset_data ) overview 概述 Add a face col…...

MacM1(ARM)安装Protocol Buffers

MacM1(ARM)安装Protocol Buffers 本文目录 MacM1(ARM)安装Protocol Buffers3.21之前版本安装使用configure3.22之后版本安装使用cmake使用编译后的版本 protobuf下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 在运行./autogen.sh或./configure命…...

使用C++从0到1实现人工智能神经网络及实战案例

引言 既然是要用C来实现&#xff0c;那么我们自然而然的想到设计一个神经网络类来表示神经网络&#xff0c;这里我称之为Net类。由于这个类名太过普遍&#xff0c;很有可能跟其他人写的程序冲突&#xff0c;所以我的所有程序都包含在namespace liu中&#xff0c;由此不难想到我…...

React Router

一、简介 react router是一个构建基于react应用的路由管理库。允许你在程序中定义不同的路由和导航规则。以实现不同的url路径显示不同的组件。 二、相关技术 <Router><div><ul id "menu"><li><Link to "/home">Home<…...

https 是否真的安全,https攻击该如何防护,https可以被抓包吗?如何防止呢?

首先解释一下什么是HTPPS 简单来说&#xff0c; https 是 http ssl&#xff0c;对 http 通信内容进行加密&#xff0c;是HTTP的安全版&#xff0c;是使用TLS/SSL加密的HTTP协议 Https的作用&#xff1a; 内容加密 建立一个信息安全通道&#xff0c;来保证数据传输的安全&am…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...