SpringBoot+Vue实现文章管理系统(前端)

一、介绍

 前端的项目同Node.js创建Vue项目,配合elementUI+Pinia+Persist实现,封装api、axios、token、Router;前端页面的设计,在本文就就不过多赘述了

二、详细代码

 2.1Api

 2.1.1 User.js

import request from '@/utils/request.js'
import { usetokenstore } from '@/stores/token.js'

 export const loginService =(loginData)=>{
   const params=new URLSearchParams();
   for(let key in loginData){
    params.append(key,loginData[key])
   }
   return  request.post('/user/login',params)
}


export const resetservice=(passwordData)=>{
  

  const  tokenstore=usetokenstore();
   
 return request.patch('/user/updatePwd',passwordData,{headers:{'jwt':tokenstore.token}})

}

export const  addservice=(userdata)=>{

  const  tokenstore=usetokenstore();
  const params=new URLSearchParams();
   for(let key in userdata){
    params.append(key,userdata[key])
   }
   
  return request.post('/user/register',params,{headers:{'jwt':tokenstore.token}})


}

export const userinfoservice = ()=>{

  const  tokenstore=usetokenstore();
return request.get('/user/userInfo',{headers:{'jwt':tokenstore.token}})

}

2.1.2 Article.js

 

import request from '@/utils/request.js'
import { usetokenstore } from '@/stores/token.js'
export const listservice =()=>{

    const tokenstore=usetokenstore();
    //响应式数据的值需要
    //pinia中定义的响应式数据,不需要.value  
    return request.get('/article/list',{headers:{'jwt':tokenstore.token}})
}


export const mylistservice =()=>{
    const tokenstore=usetokenstore();
    return request.get('/article/mylist',{headers:{'jwt':tokenstore.token}})
}

export const  addservice=(articledata)=>{

    const  tokenstore=usetokenstore();
   

    return request.post('/article',articledata,{headers:{'jwt':tokenstore.token}})


}

export const deleteservice =(id)=>{
    
    const  tokenstore=usetokenstore();
    return request.delete('/article/delete?id='+id,{headers:{'jwt':tokenstore.token}})




}
export const  updateservice =(newdate)=>{
    const  tokenstore=usetokenstore();
    return request.post('/article/update',newdate,{headers:{'jwt':tokenstore.token}})

}

2.2 request.js


import axios from "axios";
import { ElMessage } from 'element-plus'
import {usetokenstore} from'@/stores/token.js'
const baseURL ='/api';
const instance=axios.create({baseURL})



import router from '@/router'

//响应拦截器
instance.interceptors.response.use(
    result=>{
        if(result.data.code===0){
        return result.data;
        }
       // alert(result.data.msg ? result.data.mess : "事务异常")
        ElMessage.error(result.data.msg ? result.data.msg : "事务异常");
      //异步的状态化成失败的状态
       return Promise.reject(result.data)
    },
    err=>{
        if(err.response.status===401){
            ElMessage.error('请先登录')
            router.push('/login')
        }else{
            ElMessage.error('服务异常')
        }
        
        return Promise.reject(err);//异步的状态化成失败的状态
    }
)
export default instance;

2.3 token.js

import {defineStore} from 'pinia'

import{ref} from 'vue'
//使用pinia 创建储存token的仓库供各个组件使用
export const usetokenstore=defineStore('token',()=>{

   
 const token=ref('')

//修改token的值
 const setToken =(newtoken)=>{
    token.value=newtoken

 }
 //移除token的值
 const removetoken=()=>{
    token.value=''

 }

return{
    token,setToken,removetoken

}


}
,//参数持久化
{
   persist:true 
}
);

2.4 Router

import {createRouter,createWebHistory }from 'vue-router'


import loginvue from "@/views/login.vue"
import layoutvue from "@/views/layout.vue"
import ArticlelistVue from '@/views/Articlelist.vue'
import ArticleManageVue from '@/views/ArticleManage.vue'
import UseraddVue from '@/views/Useradd.vue'
import UserResetPasswordVue from '@/views/UserResetPassword.vue'


//定义路由关系
const routes =[
{
    path:'/login',component :loginvue
    
}
,
{
    path:'/',component :layoutvue,children:[{
        path:'/article/list',component :ArticlelistVue
    }
    ,
    {
        path:'/article/manage',component :ArticleManageVue
    }
    ,
    {
        path:'/user/register',component :UseraddVue
    }
    ,
    {
        path:'/user/updatePwd',component :UserResetPasswordVue
    }
    ]
}




]

//创建路由
const router =createRouter(
    {
        history:createWebHistory(),
        routes:routes
    }
)
export default router

2.5 配置

2.5.1 App.vue

<script setup>
//app.vue 只实现引入页面 进行页面的展示
</script>

<template>

<router-view> </router-view>
</template>

<style scoped>

</style>

2.5.2 main.js

const app=createApp(App);
const pinia=createPinia();
const persist=createPersistedState();
pinia.use(persist) //persist 是pinia的持久化组件 避免页面刷新丢失token 无法访问后端接口
app.use(pinia)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

 

2.6 页面

Articlelist.vue

<script setup>

import { ref } from 'vue'
//根据后端返回的数据去定义
const articles = ref([
    {
        "id": "",
        "title": "",
        "content":"",
        "user": "",
        "userid":"",
        "createTime": "",
        "updateTime": ""
    }
    
])
import {listservice} from "@/api/article.js"
const articlelist =async()=>{
   let result= await listservice();
   articles.value=result.data;
   
}
articlelist();
</script>
<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章目录</span>
                
            </div>
        </template>
        <el-table :data="articles" style="width: 100%">
            <el-table-column label="序号" width="100" type="index"> </el-table-column>
            <el-table-column label="标题" prop="title"></el-table-column>
            <el-table-column label="发布者" prop="user"></el-table-column>
            <el-table-column label="发布日期" prop="createTime"></el-table-column>
            <el-table-column label="更新日期" prop="updateTime"></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>

ArticleManager.vue

<script setup>
import {
  
    Delete
} from '@element-plus/icons-vue'
import {mylistservice,deleteservice} from'@/api/article.js'
import {addservice} from "@/api/article.js"
import { ref } from 'vue'
import { ElMessageBox , ElMessage} from 'element-plus';
const list = ref([
    {
        "id": '',
        "title": "",
        "content": "",
        "user":'',
        "userid":"",
        "createTime": "",
        "updateTime": ""
    }]
)

const dialogFormVisible = ref(false)
const formLabelWidth = '140px'
const articleInfo=ref(
  {
    id:"",
    title: "",
    description:"",
    content: "",
  }
)

//销毁对话框
const resetDialog = () => {
  articleInfo.value = {
    id:"",
    title: "",
    description:"",
    content: "",
  };
};
const deletearticle = (id) => {
      ElMessageBox.confirm(
          '你确认删除该文章吗',
          '温馨提示',
          {
            confirmButtonText: '确认',
            cancelButtonText: '取消',
            type: 'warning',
          }
      )
          .then(async () => {
            //用户点击了确认
            let result = await deleteservice(id)
            ElMessage.success(result.message?result.message:'删除成功')
            //再次调用getAllCategory,获取所有文章分类
            mylist();
          })
          .catch(() => {
            //用户点击了取消
            ElMessage({
              type: 'info',
              message: '取消删除',
            })
          })
    }

const save=async()=>{

  let result= await  addservice(articleInfo.value);

  ElMessage.success(result.msg ? result.msg : "添加成功")

}

const mylist =async()=>{
    let result=await mylistservice();
   list.value=result.data;
}
mylist();
</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="list" style="width: 100%">
            <el-table-column label="序号" width="100" type="index"> </el-table-column>
            
            <el-table-column label="标题" prop="title"></el-table-column>
            <el-table-column label="发布者" prop="user"></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="Delete" circle plain type="danger" @click="deletearticle(row.id)"></el-button>
                </template>
            </el-table-column>
            <template #empty>
                <el-empty description="没有数据" />
            </template>
        </el-table>
        <el-dialog v-model="dialogFormVisible" title="文章发布" @close="resetDialog">
        <el-form :model="articleInfo" label-width="80px" style="padding-right: 20px">
          <el-form-item label="文章标题" :label-width="formLabelWidth">
            <el-input v-model="articleInfo.title " autocomplete="off" placeholder="文章标题" />
          </el-form-item>
          <el-form-item label="文章简介" :label-width="formLabelWidth">
            <el-input v-model="articleInfo.description " autocomplete="off" placeholder="文章简介" />
          </el-form-item>
          <el-form-item label="文章内容" :label-width="formLabelWidth">
            <el-input v-model="articleInfo.content " autocomplete="off" placeholder="文章内容" />
          </el-form-item>
        </el-form>
        <div class="dialog-footer" slot="footer" slot-scope="scope">
          <el-button @click="dialogFormVisible = false" class="cancel-button">取消</el-button>
          <el-button type="primary" @click="save" class="publish-button">发布</el-button>
        </div>
      </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;
    }
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  margin-right: 20px;
  margin-bottom: 20px;
}

.cancel-button {
  margin-right: 10px;
}

.publish-button {
  margin-left: 10px;
}
</style>

layout.vue

<script setup>
import {
    Management,
    Promotion,
    UserFilled,
    SwitchButton,
    Crop,
    EditPen,
    CaretBottom
} from '@element-plus/icons-vue'

import { ElMessage,ElMessageBox } from 'element-plus';
import { usetokenstore } from '@/stores/token';
import router from '@/router';

const tokenstore= usetokenstore()
const handlecommand =()=>{

  ElMessageBox.confirm(
    '您确认要退出吗',
    '温馨提示',{
        confirmButtonText:'确认',
        cancelButtonText:'取消',
        type:"warning"
    }
  ).then(async ()=>{

     tokenstore.removetoken()
     router.push('/login')


    ElMessage({
    type:'success',
    message:'退出成功'
 })
  })
  .catch(()=>{

 ElMessage({
    type:'info',
    message:'用户取消退出'
 })

  })

     
}

</script>

<template>
    
    <el-container class="layout-container" >
        <!-- 左侧菜单 -->
        <el-aside width="200px">
           
            <el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff" router>
                <el-menu-item index="/article/list">
                    <el-icon>
                        <Management />
                    </el-icon>
                    <span>文章目录</span>
                </el-menu-item>
                <el-menu-item index="/article/manage" >
                    <el-icon>
                        <Promotion />
                    </el-icon>
                    <span>文章管理</span>
                </el-menu-item>
                <el-sub-menu >
                    <template #title>
                        <el-icon>
                            <UserFilled />
                        </el-icon>
                        <span>个人中心</span>
                    </template>
                    
                    <el-menu-item index="/user/register">
                        <el-icon>
                            <Crop />
                        </el-icon>
                        <span>添加管理</span>
                    </el-menu-item>
                    <el-menu-item index="/user/updatePwd">
                        <el-icon>
                            <EditPen />
                        </el-icon>
                        <span>重置密码</span>
                    </el-menu-item>
                </el-sub-menu>
            </el-menu>
        </el-aside>
        <!-- 右侧主区域 -->
        <el-container >
            <!-- 头部区域 -->
            <el-header >
                <div style="color: aliceblue;"><strong>管理系统</strong></div>
                <el-dropdown placement="bottom-end">
                    <el-dropdown placement="bottom-end" @command='handlecommand'>
                    <span class="el-dropdown__box">
                        
                        <el-icon>
                            <CaretBottom />
                        </el-icon>
                    </span>
                    <template #dropdown>
                        <el-dropdown-menu>
                           
                            <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
                        </el-dropdown-menu>
                    </template>
                </el-dropdown> 
                    
                </el-dropdown>
            </el-header>
            <!-- 中间区域 -->
            <el-main >
              
                <router-view></router-view>
                
            </el-main>
            <!-- 底部区域 -->
            <el-footer style="width: auto;">软件学院网站后台 ©2024 Created by ZUTER</el-footer>
        </el-container>
    </el-container>
    
</template>

<style lang="scss" scoped>
.layout-container {
    height: 100vh;
   

    .el-aside {
        background-color: #232323;

      

        .el-menu {
            border-right: none;
        }
    }

    .el-header {
        background-color: #232323;
        display: flex;
        align-items: center;
        justify-content: space-between;

       
        .el-dropdown__box {
            display: flex;
            align-items: center;
        }
            .el-icon {
                color: #ebe7e7;
                margin-left: 10px;
                margin-right: 2px;
            }

            &:active,
            &:focus {
                outline: none;
            }
        }
    
    .el-footer {
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 14px;
        color: #666;
    }
   
}
</style>

 login.vue


<script setup>
import { loginService } from '@/api/user.js'
import { ElMessage } from 'element-plus'
import { ref } from 'vue'

const loginData = ref({
  username: '',
  password: '',
  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' }  
  ]
 
}
import{usetokenstore}from '@/stores/token.js'
import {useRouter} from 'vue-router'
//创建路由
const router= useRouter()
//创建token仓库
const tokenstore=usetokenstore();
const login = async () => {
  let result = await loginService(loginData.value);
 
 ElMessage.success(result.msg ? result.msg : "登录成功")

 //将token存储在pinia
 tokenstore.setToken(result.data)
router.push('/')

}
</script>




<template>

  <el-form ref="form"  :model="loginData" label-width="65px" class="loginForm" :rules="rules">
      <h3 class="login_title">登录</h3>
      <el-form-item  label="用户名" prop ='username'>
        <el-input  placeholder="请输入用户名" v-model="loginData.username" ></el-input>
      </el-form-item >
      <el-form-item label="密码" prop ='password'>
        <el-input  type="password" placeholder="请输入密码" v-model="loginData.password"></el-input>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button type="primary" style="width: 100%;background: #505458;border: none" @click="login" >登录</el-button>
      </el-form-item>
    </el-form>
  
  </template>
  
    
  <style>
  
  .loginForm {
  width: 350px;
  margin: 120px auto;
  border: 1px solid #DCDFE6;
  padding: 20px;
  border-radius: 5px;
  box-shadow: 0 0 30px #DCDFE6;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.login_title {
  margin-bottom: 40px;
  text-align: center;
  color: #505458;
}
  </style>

Useradd.vue

<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

const userdata = ref({

      username:"",
      password:'',
      name:'',
      repassword:''



})
import {addservice} from '@/api/user'

const adduser=async()=>{

    let result= await  addservice(userdata.value);

    ElMessage.success(result.msg ? result.msg : "添加成功")





}
const checkrepassword = (rule, value, callback) => {
  if (value === "") {
    callback(new Error("请在此确认密码"));
  } else if (value !== userdata.password) {
    callback(new Error("请确保再次输入的密码一样"));
  } else {
    callback();
  }
}

const rules = {
 
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 5, max: 16, message: '请输入长度为5-16位非空字符', trigger: 'blur' }  
  ],
  name: [
    { 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-card class="page-container">
    <template #header>
            <div class="header">
                <span>添加管理员</span>
            </div>
    </template>

            <el-form ref="form" :model="userdata" :rules="rules">
                
                <el-form-item prop="username">
                    <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="userdata.username"></el-input>
                </el-form-item>
                
                <el-form-item prop="name">
                    <el-input :prefix-icon="User" placeholder="请输入真实姓名" v-model="userdata.name"></el-input>
                 </el-form-item>
                
             
                <el-form-item prop="password">
                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model='userdata.password'></el-input>
                </el-form-item>

                <el-form-item prop="repassword">
                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model='userdata.repassword'></el-input>
                </el-form-item>
              
                <el-form-item>
                    <el-button class="button" type="primary" auto-insert-space @click="adduser">
                        添加
                    </el-button>
                </el-form-item>
           </el-form>

   </el-card>
</template>

<style>
.page-container {
    min-height: 100%;
    box-sizing: border-box;

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}
</style>

UserInfo.vue

<script setup>

import { ref } from 'vue'

const list = ref([
    {
        "id": '',
        "username": "",
        "name": "",
        
    }]
)
import { userinfoservice } from '@/api/user.js'
const getinfo=async()=>{
   
    let result=await userinfoservice();

    list.value=result.data;
}
getinfo();
</script>

<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>个人信息</span>
                
            </div>
        </template>
        <el-descriptions >
    <el-descriptions-item label="Username" ></el-descriptions-item>
    <el-descriptions-item label="Id" ></el-descriptions-item>
    <el-descriptions-item label="name"></el-descriptions-item>
    
  </el-descriptions>
         
        
      
    </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>

UserResetPassword.vue 

<script setup>
import { Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

const checkrepassword = (rule, value, callback) => {
  if (value === "") {
    callback(new Error("请在次确认密码"));
  } else if (value !== passwordData.password) {
    callback(new Error("请确保再次输入的密码一样"));
  } else {
    callback();
  }
}

const rules = {
 
  oldpassword: [
    { 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' }
  ]
}

const passwordData = ref({
  
  oldpassword:'',
  password: '',
  repassword: ''
})

import {resetservice} from"@/api/user.js"

const reset = async() =>{

     let result=await resetservice(passwordData.value);
     
     ElMessage.success(result.msg ? result.msg : "修改成功")
     
}


</script>

<template>
   <el-card class="page-container">
    <template #header>
            <div class="header">
                <span>重置密码</span>
            </div>
    </template>

            <el-form ref="form" :model="passwordData" :rules="rules" >
                
               
                <el-form-item prop="oldpassword">
                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入旧密码" v-model="passwordData.oldpassword"></el-input>
                </el-form-item>
                
             
                <el-form-item prop="password">
                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入新密码" v-model="passwordData.password"></el-input>
                </el-form-item>

                <el-form-item prop="repassword">
                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="passwordData.repassword"></el-input>
                </el-form-item>
              
                <el-form-item>
                    <el-button class="button"  auto-insert-space @click="reset">
                        提交
                    </el-button>
                </el-form-item>
           </el-form>

   </el-card>
</template>

<style>
.page-container {
    min-height: 100%;
    box-sizing: border-box;

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}
</style>

三、总结

1. 项目使用Node.js创建Vue项目,配合elementUI+Pinia+Persist实现,封装了api、axios、token和Router。
2. 在api部分,定义了用户相关的接口(User.js)和文章相关的接口(Article.js),包括登录、重置密码、注册、获取用户信息等功能。
3. request.js中使用axios处理HTTP请求,并设置了基本URL,同时实现了响应拦截器用于统一处理请求结果。
4. token.js利用Pinia创建了一个用于存储token的仓库,提供了设置token和移除token的方法,并进行了持久化处理。
5. Router部分定义了项目的路由关系,包括登录页、文章列表页、文章管理页、用户注册页和用户重置密码页。
6. 配置中通过App.vue引入各页面组件进行展示,main.js中初始化了Pinia、Persist并挂载了路由和ElementPlus。
7. 整体架构清晰,实现了前后端分离、数据持久化等功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值