《基于 Koa 的登录页面实战:突破与提升》

293 阅读6分钟

《基于 Koa 的登录页面实战:突破与提升1.0》

任何的一个项目对于登录安全的管理都是必要的,我们需要对于页面设置一个权限,不同权限能访问的页面都是有管理的,并且每个用户的存储和访问的数据都是独立的,这是一个基本要求,对于后端来说,每次接受的请求都是需要带秘钥回来,并且秘钥对上了才返回信息。

1722509513784.png

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

首页

1722517587529.png

Menu

1722517594903.png

模板代码(<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
1722517610457.png

模板代码(<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样式就不作讲解了

登录页面怎么能少了注册页面呢

1722517651228.png
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创建响应式数据,usernamepasswordnickname,来存储用户名,密码和昵称。
  • 通过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>

总结

权限登录注册在系统中起着关键作用。它不仅能通过为用户设置不同的权限级别,限制其对特定页面和数据的访问,还能在注册环节收集必要信息以确认用户身份的真实性。登录过程则需用户提供准确的账号密码等凭证,经过系统验证后准许访问,从而有效保障系统的安全性和用户数据的隐私性。