《基于 Koa 的登录页面实战:突破与提升1.0》
任何的一个项目对于登录安全的管理都是必要的,我们需要对于页面设置一个权限,不同权限能访问的页面都是有管理的,并且每个用户的存储和访问的数据都是独立的,这是一个基本要求,对于后端来说,每次接受的请求都是需要带秘钥回来,并且秘钥对上了才返回信息。
vue地图-路由
src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/login",
name: "Login",
component: () => import("@/views/Login.vue"),
meta: {
title: "Login",
},
},
{
path: "/register",
name: "Register",
component: () => import("@/views/Register.vue"),
},
{
path: "/",
redirect: "/noteClass",
},
{
path: "/noteClass",
name: "NoteClass",
component: () => import("@/views/noteClass.vue"),
meta: {
title: "笔记管理",
},
},
{
path: "/noteList",
name: "NoteList",
component: () => import("@/views/NoteList.vue"),
},
{
path: "/noteDetail",
name: "noteDetail",
component: () => import("@/views/noteDetail.vue"),
meta: {
title: "笔记详情",
},
},
{
path: "/notePublish",
name: "notePublish",
component: () => import("@/views/notePublish.vue"),
meta: {
title: "笔记",
},
},
],
});
// 全局路由守卫
const whitePath = ["/login", "/register", "/noteClass"];
router.beforeEach((to, from, next) => {
Document.title = to.meta.title;
if (!whitePath.includes(to.path)) {
//需要登录
// 判断浏览器本地有无userinfo
if (!localStorage.getItem("userInfo")) {
router.push("/login");
return;
}
next();
return;
}
next();
});
export default router;
createRouter模块负责监听和跳转,router.beforeEach跳转前的判断权限,称之为路由守卫,把whitePath数组设置为白名单,不需要权限,可以直接访问,否则则需要校验浏览器是否存储登录信息,若有则允许登录,否则跳转到登录页面。
首页
src/views/noteClass.vue
首页
Menu
模板代码(<template> 部分) :
<template>
<div class="note-class-wrapper">
<div class="note-class">
<header>
<div><van-icon name="wap-nav" @click="showMenu = !showMenu"></van-icon></div>
<div>
<van-icon name="edit" @click="router.push('/notePublish')"></van-icon>
<van-icon name="lke-o"></van-icon>
<van-icon name="search"></van-icon>
</div>
</header>
<section>
<section>
<div class="note-item" v-for="(item, index) in noteClassList" :key="index"
:style="{ backgroundColor: item.bgColor }"
@click="goNoteList(item.title)"
>
<span class="title" >{{ item.title }}</span>
</div>
</section>
</section>
<Menu class="menu show" v-if="showMenu" @hidex="headle"></Menu>
</div>
</template>
这段模板代码定义了登录页面的结构。
- 页面左边包含一个
vant图标组件,并绑定点击事件控制Menu组件的展现和消失。 - 容器部分循环展现了脚本部分写好的数组,并绑定点击事件,进行跳转。
脚本代码(
<script setup>部分) :
<script setup>
import Menu from '@/components/Menu.vue';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const showMenu = ref(false);
const router = useRouter();
const goNoteList = (title) => {
router.push({ path: '/noteList', query: { title } });
}
const noteClassList = [
{ bgColor: "black", title: '美食' },
{ bgColor: "red", title: '工作' },
{ bgColor: "yellow", title: '��行' },
{ bgColor: "green", title: '运动' },
{ bgColor: "blue", title: '日常' },
{ bgColor: "purple", title: '学习' },
]
const headle = (e) => {
showMenu.value = e;
}
</script>
这段脚本代码主要处理页面的逻辑。
- 引入
ref用于创建响应式数据,showMenu,用于控制Menu组件的展现和消失。 - 通过
useRouter引入路由对象router用于页面跳转。 - 定义了点击事件,点击后触发
goNoteList方法,实现向noteList组件跳转,并携带数据,经过路由守卫,若登录则展现,否则跳转登录页。
样式代码(<style> 部分) :
<style lang="less" scoped>
.note-class-wrapper {
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
.note-class {
position: absolute;
width: 100%;
height: 100%;
padding: 0 0.5866rem;
box-sizing: border-box;
overflow-y: scroll;
transform: translateX(0%);
transition: transform 0.3s;
&.hide {
transform: translateX(100%);
}
header {
display: flex;
justify-content: space-between;
height: 1.2rem;
align-items: center;
margin: 10px 0;
.van-icon {
font-size: 24px;
margin: 0 5px;
}
}
section {
width: 100%;
.note-item {
height: 3.2rem;
border-radius: 0.3rem;
margin-bottom: 0.64rem;
box-shadow: 0 0 10px 2px #ccc;
overflow: hidden;
.title {
display: block;
margin-top: 0.5333rem;
text-align: center;
font-size: 0.5333rem;
color: #fff;
}
}
}
}
.menu {
width: 100%;
height: 100%;
position: absolute;
transform: translateX(-100%);
transition: all 0.3s;
&.show {
transform: translateX(0%);
}
}
}
</style>
用css将Menu组件通过- transform: translateX(-100%); :将元素向左平移其自身宽度的 100%,使其完全隐藏在左侧。
而 .menu.show 是一个复合选择器,表示当 .menu 元素同时具有 show 类时,会应用其中的样式。
transform: translateX(0%); 会将元素平移回初始位置,即完全显示出来。
脚本部分响应式数据showMenu控制Menu是否展现,若展现而且具有.menu.show 复合选择器,就将Menu组件显示出来。
私人信息的"指纹"验证
src/views/Login.vue
模板代码(<template> 部分) :
<template>
<div class="login">
<h1>登录</h1>
<div class="login-wrapper">
<div class="avatar">
<img src="https://q6.itc.cn/q_70/images03/20240612/0a9ee294f9a64885b6672c7421396961.jpeg" alt="">
</div>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field v-model="username" name="username" label="用户名" placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]" />
<van-field v-model="password" type="password" name="password" label="密码" placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]" />
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">
登录
</van-button>
</div>
</van-form>
</div>
<p class="register" @click="router.push('/register')">新用户?点击这里注册</p>
</div>
</template>
这段模板代码定义了登录页面的结构。
-
页面顶部有一个标题
<h1>显示“登录”。 -
包含一个
login-wrapper容器,内部有用户头像展示部分和表单部分。- 表单使用了
van-form组件,这个组件是vent组件库中表单组件,包含用户名和密码的输入字段,通过v-model进行双向数据绑定,并设置了必填规则。 - 有一个提交按钮,点击时触发
onSubmit方法。
- 表单使用了
-
页面底部有一个注册链接,点击时通过
router.push跳转到注册页面。
脚本代码(<script setup> 部分) :
<script setup>
import { ref } from 'vue';
import axios from '@/api'
import { useRouter } from 'vue-router';
const username = ref('')
const password = ref('')
const router = useRouter();
const onSubmit = async (values) => {
// 向后端发请求,将账号密码传给后端
const res = await axios.post('/user/login', values)
localStorage.setItem('userInfo', JSON.stringify(res.data));
localStorage.setItem("token", res.token)
router.push('/noteClass')
}
</script>
这段脚本代码主要处理页面的逻辑。
-
引入了
ref用于创建响应式数据username和password来存储用户名和密码的值。 -
通过
useRouter引入路由对象router用于页面跳转。 -
定义了
onSubmit方法,当表单提交时触发。该方法使用axios向后端发送登录请求,并将响应数据存储到本地存储中(浏览器中),最后通过router.push跳转到/noteClass页面。
样式代码(<style> 部分) :
</script>
<style lang="less" scoped>
.login {
width: 100vw;
height: 100vh;
background-color: #fff;
padding: 0 0.3rem;
box-sizing: border-box;
overflow: hidden;
position: relative;
h1 {
height: 0.6933rem;
font-size: 0.48rem;
text-align: center;
line-height: 0.6933rem;
margin-top: 1.12rem;
}
.login-wrapper {
width: 7.44rem;
border: 1px solid rgba(187, 187, 187, 1);
margin: 0 auto;
margin-top: 1.7rem;
border-radius: 0.3rem;
box-shadow: 0 0 0.533rem 0 rgba(170, 170, 170, 1);
padding-bottom: 15px;
.avatar {
width: 2.4rem;
height: 2.4rem;
margin: 1rem auto 0.77rem;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
}
}
}
.register {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
}
}
:deep(.van-field__label) {
width: 45px;
}
</style>
css样式就不作讲解了
登录页面怎么能少了注册页面呢
src/views/register.vue
模板代码(<template> 部分) :
<template>
<div class="login">
<h1>注册</h1>
<div class="login-wrapper">
<div class="avatar">
<img src="https://q6.itc.cn/q_70/images03/20240612/0a9ee294f9a64885b6672c7421396961.jpeg" alt="">
</div>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field v-model="username" name="username" label="用户名" placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]" />
<van-field v-model="password" type="password" name="password" label="密码" placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]" />
<van-field v-model="nickname" name="nickname" label="昵称" placeholder="昵称"
:rules="[{ required: true, message: '请填写昵称' }]" />
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">
注册
</van-button>
</div>
</van-form>
</div>
<p class="register" @click="router.push('/login')">已有账号?点击登录</p>
</div>
</template>
注册页面的模板代码跟登录页面基本一样,只是多了一个昵称的input框。
脚本代码(<script setup> 部分) :
<script setup>
import { ref } from 'vue';
import axios from '@/api'
import { useRouter } from 'vue-router';
import { showToast } from 'vant';
const username = ref('')
const password = ref('')
const nickname = ref('')
const router = useRouter()
const onSubmit = async (values) => {
const res = await axios.post('user/register', values)
showToast(res.msg)
setTimeout(() =>
router.push('/login'), 3000)
}
</script>
- 引入了
ref创建响应式数据,username,password,nickname,来存储用户名,密码和昵称。 - 通过
useRouter,创建路由对象,注册成功后3秒跳转到登录页。 - 定义了onsubmit方法,当表单提交后,axios向后端发送注册请求,并将注册成功打印出来,3秒后实现登录跳转。
样式代码(<style> 部分) :
<style lang="less" scoped>
.login {
width: 100vw;
height: 100vh;
background-color: #fff;
padding: 0 0.3rem;
box-sizing: border-box;
overflow: hidden;
position: relative;
h1 {
height: 0.6933rem;
font-size: 0.48rem;
text-align: center;
line-height: 0.6933rem;
margin-top: 1.12rem;
}
.login-wrapper {
width: 7.44rem;
border: 1px solid rgba(187, 187, 187, 1);
margin: 0 auto;
margin-top: 1.7rem;
border-radius: 0.3rem;
box-shadow: 0 0 0.533rem 0 rgba(170, 170, 170, 1);
padding-bottom: 15px;
.avatar {
width: 2.4rem;
height: 2.4rem;
margin: 1rem auto 0.77rem;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
}
}
}
.register {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
}
}
:deep(.van-field__label) {
width: 45px;
}
</style>
总结
权限登录注册在系统中起着关键作用。它不仅能通过为用户设置不同的权限级别,限制其对特定页面和数据的访问,还能在注册环节收集必要信息以确认用户身份的真实性。登录过程则需用户提供准确的账号密码等凭证,经过系统验证后准许访问,从而有效保障系统的安全性和用户数据的隐私性。