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