待办清单项目13:重构登录功能
5/16/2025
177 阅读
完成退出登录的功能
1.打开前端项目,在终端窗口运行npm install pinia
命令,安装pinia
包
2.在src
目录下创建一个pinia.js
文件,写上如下图所示的代码:
3.打开main.js文件,在如图所示的位置添加框中的代码。
4.在src目录中创建一个store
目录,然后在store
目录中创建一个userStore.js
文件
该文件的代码如下:
import { defineStore } from "pinia";
export const defineUser = defineStore("loginUser", {
state: () => ({
uid: null,
username: null,
}),
getters: {
},
actions: {
setUid(uid) {
this.uid = uid;
},
setUsername(username) {
this.username = username;
}
},
});
5.在store目录中创建todoStore.js
文件,粘贴下面的代码
import { defineStore } from "pinia";
export const defineTodoList = defineStore("todoList", {
state: () => ({
list: [
{
sid: 1,
uid: 1,
title: "吃饭",
completed: 0
},
{
sid: 2,
uid: 1,
title: "睡觉",
completed: 1
},
{
sid: 3,
uid: 2,
title: "打豆豆",
completed: 0
},
{
sid: 4,
uid: 2,
title: "打球",
completed: 1
}
]
}),
getters: {
},
actions: {
clear() {
this.list = [];
}
},
});
6.打开Header.vue文件,修改代码如下:
<script setup>
import {defineUser } from '../store/userStore.js' // 导入用户状态管理模块
import {defineTodoList} from '../store/todoStore.js' // 导入待办事项状态管理模块
import { useRouter } from 'vue-router' // 导入路由模块
const router = useRouter() // 获取路由实例
let sysUser = defineUser() // 获取用户状态管理实例
let todoList = defineTodoList() // 获取待办事项状态管理实例
const logout = () => {
// 退出登录,清空用户信息,重置待办事项列表
sysUser.uid = null
sysUser.username = null
todoList.clear()
// 跳转到登录页面
router.push('/login')
}
</script>
<template>
<header class="header">
<div class="logo">
<!-- 标题 -->
<h1>待办事项管理系统</h1>
<!-- 副标题 -->
<p>欢迎使用我们的待办事项管理系统</p>
</div>
<div class="actions">
<!-- 链接到待办事项页面的按钮 -->
<router-link to="/todolist" class="btn">待办事项</router-link>
<!-- 如果用户未登录,则显示登录和注册按钮 -->
<template v-if="sysUser.uid == null">
<router-link to="/login" class="btn btn-primary">登录</router-link>
<router-link to="/regist" class="btn btn-outline">注册</router-link>
</template>
<!-- 如果用户已登录,则显示用户名和退出登录按钮 -->
<template v-else>
<span class="username">{{ sysUser.username }}</span>
<button class="btn btn-danger" @click="logout">退出登录</button>
</template>
</div>
</header>
</template>
<style scoped>
.header {
display: flex;
justify-content: space-between; /* 使子元素在主轴上两端对齐 */
align-items: center; /* 使子元素在交叉轴上居中对齐 */
padding: 1rem 2rem; /* 设置内边距 */
background-color: #f8f9fa; /* 设置背景颜色 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
}
.logo h1 {
margin: 0; /* 去除默认的外边距 */
font-size: 1.5rem; /* 设置字体大小 */
color: #333; /* 设置字体颜色 */
}
.logo p {
margin: 0.25rem 0 0; /* 设置外边距 */
font-size: 0.9rem; /* 设置字体大小 */
color: #666; /* 设置字体颜色 */
}
.actions {
display: flex; /* 使用Flex布局 */
gap: 0.75rem; /* 设置子元素之间的间距 */
align-items: center; /* 使子元素在交叉轴上居中对齐 */
}
.username {
font-weight: 500; /* 设置字体粗细 */
margin-right: 0.5rem; /* 设置右边距 */
color: #333; /* 设置字体颜色 */
}
.btn {
text-decoration: none; /* 去除下划线 */
padding: 0.5rem 1rem; /* 设置内边距 */
border: none; /* 去除边框 */
border-radius: 4px; /* 设置圆角 */
cursor: pointer; /* 设置鼠标悬停时的光标样式 */
font-size: 0.9rem; /* 设置字体大小 */
transition: all 0.2s; /* 设置过渡效果 */
}
.btn:hover {
opacity: 0.9; /* 设置鼠标悬停时的透明度 */
}
.btn-primary {
background-color: #4361ee; /* 设置按钮背景颜色 */
color: white; /* 设置按钮字体颜色 */
}
.btn-outline {
background-color: transparent; /* 设置按钮背景颜色为透明 */
border: 1px solid #4361ee; /* 设置按钮边框 */
color: #4361ee; /* 设置按钮字体颜色 */
}
.btn-danger {
background-color: #ef476f; /* 设置按钮背景颜色 */
color: white; /* 设置按钮字体颜色 */
}
</style>
7.打开IDEA后端代码,找到sysUserController文件,把login函数里面最后一个else里面的代码改成下面这样:
8.修改前端Login.vue文件代码如下:
<script setup>
import { ref, reactive } from 'vue'
import request from '../utils/request'
import { useRouter } from 'vue-router'
import {defineUser } from '../store/userStore.js' // 导入用户状态管理模块
let sysUser = defineUser() // 实例化用户状态管理模块
const router = useRouter()
let loginUser = reactive({
username: "",
user_pwd: ""
})
let usernameMsg = ref("")
let userPwdMsg = ref("")
function checkUsername(){
// 用正则表达式验证用户名
// 用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位
const reg = /^[A-Za-z](?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{7,}$/;
if (reg.test(loginUser.username)) {
usernameMsg.value = '';
return true;
} else {
usernameMsg.value = '用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位';
return false;
}
};
function checkPassword(){
// 用正则表达式验证密码
const reg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
if (reg.test(loginUser.user_pwd)) {
userPwdMsg.value = '';
return true;
} else {
userPwdMsg.value = '// 密码至少包含字母、数字、特殊字符中的两种,长度至少6位';
return false;
}
};
async function login() {
var flag = checkUsername() && checkPassword();
if (!flag) {
return;
}
let {data} = await request.post('TodoList/user/login',loginUser);
if (data.code === 200) {
console.log(data);
sysUser.setUid(data.data.loginUser.uid);
sysUser.setUsername(data.data.loginUser.username);
router.push('/todolist');
} else if (data.code === 503) {
alert("密码错误");
} else if (data.code === 502) {
alert("用户名不存在");
}
else {
alert("未知错误,登录失败");
}
};
</script>
<template>
<div class="login-wrapper">
<div class="login-card">
<div class="login-header">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-square"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>
<h2>用户登录</h2>
<p>欢迎回来!请登录您的账户。</p>
</div>
<form @submit.prevent="handleLogin" class="login-form">
<div class="input-group">
<label for="username">用户名</label>
<input
type="text"
id="username"
v-model="loginUser.username"
@blur="checkUsername()"
placeholder="请输入用户名"
required
/>
<div v-if="usernameMsg" class="error-msg">{{ usernameMsg }} </div>
</div>
<div class="input-group">
<label for="password">密码</label>
<input
type="password"
id="password"
v-model="loginUser.user_pwd"
@blur = "checkPassword()"
placeholder="请输入密码"
required
/>
<div v-if="userPwdMsg" class="error-msg">{{ userPwdMsg }} </div>
</div>
<button type="submit" class="btn-login" @click="login()">登 录</button>
</form>
<div class="login-footer">
<router-link to="/regist">还没有账号?立即注册</router-link>
</div>
</div>
</div>
</template>
<style scoped>
.error-msg {
color: red;
font-size: 0.8rem;
margin-top: 0.2rem;
}
.login-wrapper {
/* min-height: 100vh; */ /* 移除 min-height */
flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
display: flex;
justify-content: center;
align-items: center;
/* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
padding: 2rem; /* 保留内边距 */
}
.login-card {
background: white;
padding: 2.5rem 3rem;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 420px;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.login-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}
.login-header {
margin-bottom: 2rem;
}
.login-header svg {
color: #4361ee;
margin-bottom: 0.5rem;
}
.login-header h2 {
font-size: 1.8rem;
color: #333;
margin-bottom: 0.5rem;
}
.login-header p {
color: #777;
font-size: 0.95rem;
}
.login-form {
display: flex;
flex-direction: column;
gap: 1.5rem; /* 增加输入框间距 */
}
.input-group {
text-align: left;
}
.input-group label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
font-size: 0.9rem;
}
.input-group input {
width: 100%;
padding: 0.8rem 1rem; /* 增加内边距 */
border: 1px solid #ddd;
border-radius: 6px; /* 更圆润的边角 */
font-size: 1rem;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
box-sizing: border-box; /* 确保 padding 不会撑大元素 */
}
.input-group input:focus {
outline: none;
border-color: #4361ee;
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1); /* 添加聚焦光晕效果 */
}
.btn-login {
padding: 0.9rem; /* 增加按钮内边距 */
background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%); /* 渐变背景 */
color: white;
border: none;
border-radius: 6px;
font-size: 1.1rem; /* 增大字体 */
font-weight: 600; /* 加粗字体 */
cursor: pointer;
transition: all 0.3s ease;
margin-top: 1rem; /* 与上方输入框的间距 */
letter-spacing: 1px; /* 增加字间距 */
}
.btn-login:hover {
background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
transform: translateY(-2px);
}
.login-footer {
margin-top: 1.5rem;
font-size: 0.9rem;
}
.login-footer a {
color: #4361ee;
text-decoration: none;
transition: color 0.3s ease;
}
.login-footer a:hover {
color: #3451db;
text-decoration: underline;
}
</style>
9.启动Tomcat服务器,在前端页面中去登录,可以发现网页右上角出现了用户名和退出登录按钮
点击退出登录,可以看到网页右上角又变成了登录和注册按钮,且页面跳转到了登录页
未登录限制访问
1.未登陆时在浏览器访问代表事项,发现可以直接访问,我们要把它改成只有登录之后才能访问
2.找到router.js
文件,把代码改成下面这样
import { createRouter, createWebHashHistory } from 'vue-router' // 从vue-router中导入createRouter和createWebHashHistory函数
import Header from '../components/Header.vue' // 导入Header组件
import Login from '../components/Login.vue' // 导入Login组件
import Regist from '../components/Regist.vue' // 导入Regist组件
import ShowTodoList from '../components/ShowTodoList.vue' // 导入ShowTodoList组件
import pinia from '../pinia.js' // 导入pinia实例
import {defineUser } from '../store/userStore.js' // 导入用户状态管理模块
let sysUser = defineUser(pinia) // 实例化用户状态管理模块
// 创建路由实例
const router = createRouter({
history: createWebHashHistory(), // 使用hash模式的路由历史记录管理
routes: [ // 定义路由配置
{ // 首页路由配置
path: '/', // 路径为根路径
redirect: '/todolist', // 路由名称为'Header'
},
{ // 登录页面路由配置
path: '/login', // 路径为/login
name: 'Login', // 路由名称为'Login'
component: Login // 对应的组件为Login
},
{ // 注册页面路由配置
path: '/regist', // 路径为/regist
name: 'Regist', // 路由名称为'Regist'
component: Regist // 对应的组件为Regist
},
{ // 显示待办事项列表页面路由配置
path: '/todolist', // 路径为/todolist
name: 'ShowTodoList', // 路由名称为'ShowTodoList'
component: ShowTodoList // 对应的组件为ShowTodoList
},
]
})
// 路由 的全局前置守卫 判断是否可以访问todolist页面
router.beforeEach((to, from, next) => {
if (to.path == '/todolist') {
// 登陆过放行
// 没登录 回到登录页
if (sysUser.username == null) {
next("/login")
} else {
next()
}
} else {
next()
}
})
export default router // 导出路由实例
3.再次在未登录的时候尝试访问待办事项页,可以发现网页强行跳转到了登录页。
若登录之后,则可以正常访问
评论 (0)
暂无评论,来发表第一条评论吧!