1.登录模块
1.注册
1.页面搭建
2.数据绑定
(1)定义数据模型->参考接口文档给属性起名
//定义数据模型
const registerData = ref({
username: '',
password: '',
rePassword: ''
})
(2)将数据模型与表单绑定->
:model="registerData"与el-form
v-model="registerData.username"与el-input (每个数据域一个输入框)...
(3)表单校验
1)定义表单校验规则
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' }
]
}
2)定义校验密码的函数
const checkRepassword = (rule, value, callback) => {
if (value == '') {
callback(new Error('请输入密码'))
} else if (value != registerData.value.password) {
callback(new Error('两次输入的密码需要一致'))
} else {
callback();
}
}
3)el-form标签通过rules属性,绑定校验规则;el-form-item标签通过prop属性,绑定校验项
<el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">
<el-form-item>
<h1>注册</h1>
</el-form-item>
<el-form-item prop="username">
<el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"
v-model="registerData.password"></el-input>
</el-form-item>
<el-form-item prop="rePassword">
<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>
3.接口调用
(1)提供调用注册接口的函数
//导入request请求工具
import request from '@/utils/request.js'
export const userRegisterService = (registerData)=>{
//借助UrlSearchParams完成传递,直接传 传递的是JSON格式
const params = new URLSearchParams();
for(let key in registerData){
params.append(key,registerData[key]);
}
return request.post('/user/register',params);
}
(2)调用后台接口,完成注册
import {userRegisterService} from '@/api/user.js'
const register = async ()=>{
let result = await userRegisterService(registerData.value);
if(result.code==0){
alert(result.msg?result.msg:'注册成功');
}else{
alert('注册失败')
}
}
(3)绑定按钮
<el-button class="button" type="primary" auto-insert-space @click="register">
注册
</el-button>
(4)添加响应拦截器
//定制请求的实例
//导入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.跨域问题
由于浏览器的同源策略限制,向不同源发送Ajax的请求会失败.需要配置代理
配置代理:
request.js中配置baseURL的值为/api
vite.config.js中配置
server:{
proxy:{
'/api':{//获取路径中包含了/api的请求
target:'http://localhost:8080',//后台服务所在的源
changeOrigin:true,//修改源
rewrite:(path)=>path.replace(/^\/api/,'')//api替换为''
}
}
}
2.登录
1.页面控制
1.登录与注册在同一个页面中,使用'注册'和'登录'的按钮控制二者表单的显示
使用一个变量控制两个表单的显示
//控制注册与登录表单的显示, 默认显示登录
const isRegister = ref(false)
两个表单再分别使用v-if和v-else控制
<el-form ref="form" size="large" autocomplete="off" v-if="isRegister"
:model="registerData" :rules="rules">
<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 class="flex">
<el-link type="info" :underline="false" @click="isRegister = true">
注册 →
</el-link>
</el-form-item>
</el-form>
2.由于注册和登录的表单复用同一个数据,故在页面切换后数据仍然存在,需要在两个表单切换时将数据清空
定义函数
//定义函数,用于清空数据模型的数据
const clearRegisterData = ()=>{
registerData.value={
username:'',
password:'',
rePassword:''
}
}
绑定事件
<el-link type="info" :underline="false" @click="isRegister = false;clearRegisterData()">
← 返回
</el-link>
<el-link type="info" :underline="false" @click="isRegister = true;clearRegisterData()">
注册 →
</el-link>
2. 数据绑定
绑定数据,复用注册表单的数据模型(直接用注册的)
表单数据校验,复用注册表单的(直接用注册的)
3.接口调用
编写登录函数并绑定按钮
import {userLoginService} from '@/api/user.js'
const login = async ()=>{
let result = await userLoginService(registerData.value);
if(result.code==0){
alert(result.msg?result.msg:'登录成功');
}else{
alert('登录失败')
}
}
4.优化拦截器
将vue中编写的函数中的,每次调用接口后判断响应结果的 判断抽取出来,放到axios拦截器(request.js)中; vue中则不需要再进行判断,只要调用成功就成功
//添加响应拦截器
instance.interceptors.response.use(
result=>{
if(result.data.code==0){
return result.data;
}else{
ElMessage.error(result.data.msg?result.data.msg:'服务异常');
return Promise.reject(result.data);
}
},
err=>{
ElMessage.error('服务异常');
return Promise.reject(err);//异步的状态转化成失败的状态
}
)
//注册
import {userRegisterService} from '@/api/user.js'
const register = async ()=>{
let result = await userRegisterService(registerData.value);
ElMessage.success(result.msg?result.msg:'注册成功');
}
//登录函数
import {userLoginService} from '@/api/user.js'
const login = async ()=>{
let result = await userLoginService(registerData.value);
ElMessage.success(result.msg?result.msg:'登录成功');
}
2.主页面搭建
1.页面搭建
2.使用路由->页面跳转
1.vue-router
(1)安装->npm install vue-router@4
(2)在src/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;
(3)在main.js中注册Vue实例并使用
import router from '@/router/index.js'
const app = createApp(App);
app.use(router);
(4)声明router-view标签,展示组件
<template>
<router-view></router-view>
</template>
(5)修改完成登录的函数,使路由完成跳转,跳转到首页
import {useRouter} from 'vue-router'
const router = useRouter();
const login = async ()=>{
let result = await userLoginService(registerData.value);
ElMessage.success(result.msg?result.msg:'登录成功');
//跳转到首页,借助路由完成跳转
router.push('/');
}
3.子路由->子页面切换
1.配置子路由
import ArticleCategory from '@/article/ArticleCategory.vue';
import ArticleManage from '@/article/ArticleManage.vue';
import UserInfo from '@/user/UserInfo.vue';
import UserAvatar from '@/user/UserAvatar.vue';
import UserResetPassword from '@/user/UserResetPassword.vue';
const routes = [
{path:'/login',component:LoginVue},
{
path:'/',
component:LayoutVue,
redirect:'/login',
children:[
{path:'/article/category',component:ArticleCategory},
{path:'/article/manage',component:ArticleManage},
{path:'/user/info',component:UserInfo},
{path:'/user/avartar',component:UserAvatar},
{path:'/user/password',component:UserResetPassword}
]
}
]
设置默认重定向的子路由(默认展示的页面)
redirect:'/article/category',
2.路由展示
声明router-view标签->在需要的地方直接添加<router-view></router-view>
3.为菜单项el-menu-item设置index属性,设置点击后的路由路径
<el-menu-item index="/article/category"></el-menu-item>
<el-menu-item index="/article/manage"></el-menu-item>
<el-menu-item index="/user/info"></el-menu-item>
<el-menu-item index="/user/avartar"></el-menu-item>
<el-menu-item index="/user/password"></el-menu-item>
3.文章分类模块
1.页面搭建
1.在article.js中定义文章分类列表查询函数
import request from "@/utils/request.js";
//文章分类列表查询
export const articleCategoryListService = ()=>{
return request.get('/category');
}
2.在ArticleCategory.vue中声明一个异步的函数,用于调用article.js中的articleCategoryListService方法,并且直接调用,进入页面后自动启动查询
import {articleCategoryListService} from '@/api/article.js'
const articleCategoryList = async ()=>{
let result = await articleCategoryListService();
categorys.value = result.data;
}
articleCategoryList();
2.Pinia状态管理库
1.Pinia基本使用
允许跨组件或页面共享状态(相当于SpringBoot的TreadLocal)
1.在main.js中注册Vue实例并使用
import { createPinia } from 'pinia'
const pinia = createPinia();
app.use(pinia);
2.在src/store/token.js中定义store
//定义store
import { defineStore } from "pinia";
import { ref } from "vue";
//参数1:名字,唯一
//参数2:函数,函数内部可以定义状态的所有内容
export const useTokenStore = defineStore('token',()=>{
//定义状态的内容
//1.响应式变量
const token = ref('')
//2.定义函数,修改token的值
const setToken = (newToken)=>{
token.value = newToken;
}
const removeToken = ()=>{
token.value = '';
}
return{
token,setToken,removeToken
}
});
export default useTokenStore;
3.在组件中使用store
(1)在登录后将得到的token存储到pinia中
import {useTokenStore} from '@/stores/token.js'
const tokenStore = useTokenStore();
const login = async ()=>{
let result = await userLoginService(registerData.value);
ElMessage.success(result.msg?result.msg:'登录成功');
//把得到的token存储到pinia中
tokenStore.setToken(result.data);
//跳转到首页,借助路由完成跳转
router.push('/article/category');
}
(2)在进行函数操作时传入token(后台需要验证token才能操作)
import {useTokenStore} from "@/stores/token.js";
//文章分类列表查询
export const articleCategoryListService = ()=>{
const tokenStore = useTokenStore();
//在pinia中定义的响应数据都不需要.value
return request.get('/category',{headers:{'Authorization':tokenStore.token}});
}
(3)添加Axios请求拦截器将token添加到请求头,可以不用每次在请求时手动向请求上添加token
//添加请求拦截器
import useTokenStore from '@/stores/token';
instance.interceptors.request.use(
(config)=>{
//请求前的回调
//添加token
let tokenStore = useTokenStore();
//判断有没有token
if(tokenStore.token){
config.headers.Authorization=tokenStore.token;
}
return config;
},
(err)=>{
//请求错误的回调
Promise.reject(err);
}
)
2.Pinia持久化插件->persist
pinia是内存存储,当刷新浏览器时会丢失数据;persist插件可以将pinia中的数据持久化的存储
(1)在pinia中使用persist,在main.js中的pinia实例中使用persist
import { createPersistedState } from 'pinia-persistedstate-plugin'
const pinia = createPinia();
const persist = createPersistedState();
//因为persist是在pinia中使用persist,而不是在app中使用
//所以要用pinia.use(persist)
pinia.use(persist);
(2)定义状态Store时指定持久化配置参数
export const useTokenStore = defineStore(
'token',
() => {
//1.响应式变量
const token = ref('')
//2.定义函数,修改token的值
const setToken = (newToken) => {
token.value = newToken;
}
const removeToken = () => {
token.value = '';
}
//返回参数
return {
token, setToken, removeToken
}
},
{
//
//持久化存储
persist: true
}
);
3.未登录统一处理
修改响应拦截器,在响应拦截器中定义的异常事件处理中判断异常代码
如果,如果响应状态码为401,则证明未登录,提示后跳转到登录页面
//添加响应拦截器
instance.interceptors.response.use(
result=>{
if(result.data.code==0){
return result.data;
}else{
ElMessage.error(result.data.msg?result.data.msg:'服务异常');
return Promise.reject(result.data);
}
},
err=>{
//判断响应状态码,如果为401,则证明未登录,提示后跳转到登录页面
if(err.response.status==401){
ElMessage.error('请先登录');
router.push('/login')
}else{
ElMessage.error('服务异常');
}
return Promise.reject(err);//异步的状态转化成失败的状态
}
)
4.添加文章分类
1.弹窗表单
定义el-dialog弹窗
(1)使用v-model绑定响应式数据,让此响应式数据当按钮点击时变化true或false
当dialogVisible为false时弹窗不显示,默认为false;点击添加分类后修改为true->显示弹窗
(2)使用:title绑定响应式数据作为弹窗标题
点击添加分类后dialogTitle修改为'添加分类';点击修改按钮后dialogTitle修改为'修改分类'
确认按钮可以根据dialogTitle的值为 添加分类或修改分类 调用不同的函数 添加或修改
(3)定义分类数据模型,使用v-model绑定数据到输入框
//控制添加分类弹窗
const dialogVisible = ref(false)
//添加分类数据模型
const categoryModel = ref({
categoryName: '',
categoryAlias: ''
})
<el-dialog v-model="dialogVisible" :title="dialogTitle" 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="dialogTitle == '添加分类' ? insertData() : modifyData()"> 确认 </el-button>
</span>
</template>
</el-dialog>
(4)使用prop绑定表单校验
//添加分类表单校验
const rules = {
categoryName: [
{ required: true, message: '请输入分类名称', trigger: 'blur' },
],
categoryAlias: [
{ required: true, message: '请输入分类别名', trigger: 'blur' },
]
}
修改ref对象中的值:
有数据模型
const categoryModel = ref({
categoryName: '',
categoryAlias: ''
})
若要修改categoryName的值,无法用将数据模型当成对象的形式修改
有两种方式
//方法1:整个对象更改
categoryModel.value = {
categoryName: '',
categoryAlias: ''
}
//方法2:只更改某个属性值
categoryModel.value['categoryName'] = '';
categoryModel.value['categoryAlias'] = '';
2.定义方法
在article.js中定义异步请求,直接传递数据,传递的为json格式
//添加文章分类
export const articleCategoryInsertService = (insertData) => {
//如果直接传 传递的是JSON格式
return request.post('/category', insertData);
}
3.定义按钮相应事件
在事件中先调用article.js中请求方法,根据返回的结果提示信息
并将dialogVisible.value设为false->弹窗不可见
然后再次调用文章分类查询,重新刷新页面数据
//提交
import { articleCategoryInsertService } from '@/api/article.js'
import { ElMessage } from 'element-plus';
const insertData = async () => {
let result = await articleCategoryInsertService(categoryModel.value);
ElMessage.success(result.msg ? result.msg : '添加成功');
dialogVisible.value = false;
articleCategoryList();
}
5.修改文章分类
1.定义方法modifyDialog,当用户点击修改按钮时调用,弹出窗口
修改弹窗的标题,并且将表单categoryModel中数据设置为用户点击的那一行的数据
template #default="{ row }" 是一个在Vue中使用的语法,一般在Vue的表格组件(el-table)
中使用
{row}
是一个参数,可以使用{row}
参数来访问当前行的数据,且自带row.id可以获取当前的id值
const modifyDialog = async (row) => {
dialogTitle.value = "修改分类";
categoryModel.value = {
categoryName: row.categoryName,
categoryAlias: row.categoryAlias,
//扩展ID属性,将来需要传递给后台,完成数据修改
id: row.id
}
dialogVisible.value = true;
}
2.在article.js中定义方法,用于发送请求,数据格式json
//修改文章分类
export const articleCategoryModifyService = (modifyData) => {
return request.put('/category', modifyData)
}
3.定义方法调用article.js中的数据,并在完成之后将弹窗设为不可见(dialogVisible设为false),且再次刷新数据
//修改
import { articleCategoryModifyService } from '@/api/article.js'
const modifyData = async () => {
let result = articleCategoryModifyService(categoryModel.value);
ElMessage.success(result.msg ? result.msg : '修改成功');
dialogVisible.value = false;
articleCategoryList();
}
6.删除文章分类
1.在article.js中定义方法,用于发送请求
//删除文章分类
export const articleCategoryDeleteService = (id) => {
return request.delete('category?id='+id)
}
2.定义方法调用article.js中的数据,点击后出现选择弹窗->直接使用Element提供的ElMessageBox.confirm
//删除
import { ElMessageBox } from 'element-plus';
import { articleCategoryDeleteService } from '@/api/article.js'
const deleteData = async (row) => {
ElMessageBox.confirm(
'你确认删除该分类信息吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
//用户点击了确认
ElMessage({
type: 'success',
message: '删除成功',
})
await articleCategoryDeleteService(row.id);
articleCategoryList();
})
.catch(() => {
//用户点击了取消
ElMessage({
type: 'info',
message: '取消删除',
})
});
}
4.文章模块
1.页面搭建
1.文章数据模型
使用ref([ ])数组存储数据
2.分页条
//用户搜索时选中的分类id
const categoryId = ref('')
//用户搜索时选中的发布状态
const state = ref('')
//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(3)//每页条数
//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {
pageSize.value = size;
categoryList();
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {
pageNum.value = num;
categoryList();
}
2.文章列表
1.在article.js中定义文章列表查询函数
//查询文章
// 参数多时可以用json格式传递,发送请求时就直接用{params:params}的形式传递
export const articleListService = (params)=>{
return request.get('/article',{params:params})
}
{params:params}可以声明不定长数目的参数数量->
发送请求时就直接用{params:params}的形式传递->传递未知数目的参数(json中的所有参数)
在调用方法时传递json格式数据,直接使用{params:params}请求传递json中的所有参数
2.在ArticleManage.vue中声明一个异步的函数,用于调用article.js中的articleListService方法,并且直接调用,进入页面后自动启动查询
//获取文章列表
import { articleListService } from '@/api/article.js'
const articleList = async () => {
//在调用方法时传递json格式数据
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;
//处理数据,使用文章分类的id获取文章分类的名称
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;
}
}
}
}
articleList();
3.复选框内容绑定已有数据
在ArticleManage.vue中查询文章分类列表,并将数据绑定在category.value中
//复选框中查询文章分类并显示
import { articleCategoryListService } from '@/api/article.js'
const categoryList = async () => {
let result = await articleCategoryListService();
categorys.value = result.data;
}
categoryList();
4.文章根据分类绑定事件
1.搜索
给搜索按钮绑定事件,调用articleList()方法
articleList()方法中传递了文章的筛选条件,使用params传递数据,获得查询结果
2.重置
重置事件直接将categoryId和state清空后再次调用articleList()方法,获得无筛选条件的文章数据
5.添加文章
1.页面构建
1.添加抽屉组件,使用visibleDrawer的false或true控制组件的显示
2.添加表单,表单绑定数据articleModel;
表单中使用富文本组件->使用vue提供的组件(需要先导入)
import { QuillEditor } from '@vueup/vue-quill'
3.上传封面
封面上传到阿里云os上传完成后进行回调显示
//导入token
import useTokenStore from '@/stores/token.js';
const tokenStore = useTokenStore();
//回调函数
const uploadSuccess = (result) => {
articleModel.value.coverImg = result.data;
console.log(result.data);
}
<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>
缩写
: 是v-bind的缩写,在on-success前没有添加括号,找了很久问题
@ 是src的路径缩写
关于函数调用时加不加括号的问题
(1)如果函数不传参,推荐不加括号
(2)调用方法时不加(),则默认也会把事件对象传递过来
(3)调用函数时,如果加上(),则默认不会把对象传递过来
2.绑定事件
1.在article.js中定义添加文章函数,使用json作为参数
//添加文章
export const articleInsertService = (articleData) => {
return request.post('/article', articleData);
}
2.点击添加文章按钮后是控制抽屉的visibleDrawer设置为true,在ArticleManage.vue中声明一个异步的函数,用于调用article.js中的articleInsertService方法
//添加
import { articleInsertService } from '@/api/article.js'
import { ElMessage } from 'element-plus'
const articleInsert = (articleState) => {
//调用方法
articleModel.value.state = articleState;
let result = articleInsertService(articleModel.value);
ElMessage.success(result.state ? result.state : '发布成功');
//抽屉不可见
visibleDrawer.value = false;
//刷新列表
articleList();
}
<el-form-item>
<el-button type="primary" @click="articleInsert('已发布')">发布</el-button>
<el-button type="info" @click="articleInsert('草稿')">草稿</el-button>
</el-form-item>
state状态分别使用两个按钮控制,两个按钮调用同一个方法,只不过传递的参数不同
若点击发布->传递参数'已发布' 若点击草稿->传递参数'草稿'
6.删除文章
1.在article.js中定义删除文章函数
//删除文章
export const articleDeleteService = (id) => {
return request.delete('article?id=' + id);
}
2.在ArticleManage.vue中声明一个异步的函数,用于调用article.js中的articleDeleteService方法
//删除
import { ElMessageBox } from 'element-plus';
import { articleDeleteService } from '@/api/article.js'
const deleteArticle = async (row) => {
ElMessageBox.confirm(
'你确认删除该文章信息吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
//用户点击了确认
ElMessage({
type: 'success',
message: '删除成功',
})
await articleDeleteService(row.id);
articleList();
})
.catch(() => {
//用户点击了取消
ElMessage({
type: 'info',
message: '取消删除',
})
});
}
7.修改文章
本功能需要先定义一个获取文章详细信息的函数,因为数据库的设计中,文章信息内只有文章分类的id,而没有文章分类的名字,故需要先获取到文章详细信息才能展示到页面
1.在article.js中定义文章详细信息查询函数
//获取文章详细信息
export const articleDetailService = (id) => {
return request.get('/article/detail?id=' + id);
}
2.在ArticleManage.vue中声明一个异步的函数,用于调用article.js中的articleDetailService方法,在用户点击后根据id查询文章详细信息,并赋值给文章数据模型article,设置弹窗可见展示给用户
//控制添加分类弹窗
const dialogVisible = ref(false);
const article = ref({
id:'',
title: '',
content: '',
coverImg:'',
state: '',
categoryId:'',
categoryName: ''
})
import { articleDetailService } from '@/api/article.js'
const dialogModify = async (row) => {
let result = await articleDetailService(row.id);
for (let i = 0; i < categorys.value.length; i++) {
if (result.data.categoryId == categorys.value[i].id) {
article.value = {
id: result.data.id,
title: result.data.title,
content: result.data.content,
coverImg: result.data.coverImg,
state: result.data.state,
categoryId: categorys.value[i].categoryId,
categoryName: categorys.value[i].categoryName
}
}
}
dialogVisible.value = true;
}
3.在article.js中定义文章列表查询函数
//修改文章
export const articleModifyService = (articleData) => {
return request.put('/article',articleData)
}
4.在ArticleManage.vue中声明一个异步的函数,用于调用article.js中的articleModifyService方法,在用户点击修改按钮后直接将文章数据模型article作为参数传递即可
import { articleModifyService } from '@/api/article.js'
const modifyArticle = async () => {
let result = await articleModifyService(article.value);
ElMessage.success(result.state ? result.state : '修改成功');
}
下拉列表绑定动态数据
<el-form-item label="文章分类">
<el-select placeholder="请选择" v-model="article.categoryName">
<el-option v-for="c in categorys" :key="c.categoryName" :label="c.categoryName" :value="c.categoryName">
</el-option>
</el-select>
</el-form-item>
5.用户模块
1.顶部导航栏
1.顶部导航栏信息显示
1.新建userInfo.js创建并使用Pinia Store
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;
2.在Layout.vue中将用户的信息存储到Pinia中,便于获取和更新
//调用函数,获取用户详细信息
import { userInfoService } from '@/api/user.js'
import useUserInfoStore from '@/stores/userInfo.js'
const userInfoStore = useUserInfoStore();
const getUserInfo = async () => {
let result = await userInfoService();
//数据存储到pinia中
userInfoStore.setInfo(result.data);
}
getUserInfo();
调用user.js中的userInfoService获取用户详细信息并存储到Pinia中
<div><strong>{{ userInfoStore.info.nickname }}</strong></div>
在使用Pinia存储的信息时,可以直接调用获取到相关信息
2.顶部下拉菜单功能实现
功能:
顶部下拉菜单的'基本资料''更换头像''重置密码'需要使用路由进行页面跳转
退出登录点击后弹出确认框,确认退出后清除pinia和token中的用户信息,并使用路由跳转到登录页面
实现:
下拉菜单使用@command绑定事件,事件中可以传递一个command参数,下拉菜单的每个条目点击后传递一个自定义的command值,可以在事件中通过判断command的值进行操作
<!-- 下拉菜单 -->
<!-- command:条目被点击后触发,事件函数上可以声明一个参数,接收条目对应指令 -->
<el-dropdown placement="bottom-end" @command="handleCommand">
<span class="el-dropdown__box">
<el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
<el-dropdown-item command="avartar" :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>
//条目被点击后调用的函数
import {useRouter} from 'vue-router'
import { ElMessageBox,ElMessage } from 'element-plus';
import {useTokenStore} from '@/stores/token.js'
const tokenStore = useTokenStore();
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);
}
}
command的值直接定义为路由的路径值,在判断用户点击的不是退出登录后,直接使用路由的push跳转到对应界面
判断为退出登录后清除Pinia中的token和用户信息
2.基本资料修改
1.显示用户信息
直接从Pinia中获取用户详细信息数据并赋值给数据模型显示
//显示用户信息
import useUserInfoStore from '@/stores/userInfo.js';
const userInfoStore = useUserInfoStore();
const userInfo = ref({
id: '',
username: '',
nickname: '',
email: '',
})
const getUserInfo = () => {
userInfo.value = {
id: userInfoStore.info.id,
username: userInfoStore.info.username,
nickname: userInfoStore.info.nickname,
email: userInfoStore.info.email,
}
}
getUserInfo();
2.提交修改用户信息
1.在user.js中定义用户信息修改函数
//修改用户信息
export const userModifyService = (userData) => {
return request.put('/user/update',userData);
}
2. 在UserInfo.vue中声明一个异步的函数,用于调用user.js中的userModifyService方法,在用户点击后发送请求修改用户信息
//修改用户信息
import { ElMessage } from 'element-plus';
import { userModifyService } from '@/api/user.js'
const modifyInfo = async () => {
let result = await userModifyService(userInfo.value);
ElMessage.success(result.state?result.state:'修改成功');
userInfoStore.setInfo(result.data);
}
3.用户头像修改
1.在user.js中定义修改头像函数
//修改头像
export const userAvatarUpdateService = (avatarUrl)=>{
const params = new URLSearchParams();
params.append('avatarUrl',avatarUrl)
return request.patch('/user/updateAvatar',params);
}
2. 在UserInfo.vue中声明一个异步的函数,用于调用user.js中的userModifyService方法,在用户点击后发送请求修改用户信息
import {userAvatarUpdateService} from '@/api/user.js'
import { ElMessage } from 'element-plus'
const updateAvatar = async () => {
//调用接口
let result = await userAvatarUpdateService(imgUrl.value);
ElMessage.success(result.msg?result.msg:'修改成功');
//修改Pinia中的数据
userInfoStore.info.userPic = imgUrl.value;
}