一、介绍
前端的项目同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. 整体架构清晰,实现了前后端分离、数据持久化等功能