【一】用Vue3+Typescript+nodejs完成一个功能完整的登录系统

328 阅读2分钟

前言

从这个项目开始,会通过模块的方式,将日常练习的功能迭代其中,在不断完善这个系统的同时,也会梳理完整的设计流程。这篇笔记是通过学习实践输出的项目成果,以下是在开发过程中做的笔记,可以作为学习参考。

环境准备

运行环境

  • Node.js
  • VScode
  • Vue CLI

技术框架

  • Vue3
  • Typescript
  • express

创建前端项目

项目初始化

创建一个新的Vue3项目,并添加Typescript语法支持、路由、状态管理

npm create vue@latest
cd workbench
npm install

企业微信截图_20241019120012.png

安装依赖

npm install axios # 接口调用插件

项目结构

workbench/
├── src/
│   ├── api/
│   ├── ├── index.ts
│   ├── ├── login.ts
│   ├── assets/
│   ├── components/
│   ├── router/
│   ├── ├── index.ts
│   ├── stores/
│   ├── views/  
│   ├── ├── Forgot/
│   ├── ├── ├── ForgotView.vue
│   ├── ├── Home/
│   ├── ├── ├── HomeView.vue
│   ├── ├── Login/
│   ├── ├── ├── LoginView.vue
│   ├── ├── Register/
│   ├── ├── ├── RegisterView.vue
│   ├── App.vue  
│   ├── main.ts 
├── package.json
├── .gitignore
├── vite.config.ts
├── README.md

创建后端项目

项目初始化

mkdir benchserve
npm init

安装依赖

npm install body-parser # 响应数据解析插件
npm install express # 服务框架
npm install nodemailer # 邮件收发插件
npm install sqlite3 # 数据库操作插件
npm install crypto-js # 加密插件

项目结构

benchserve/
├── src/
│   ├── common/
│   ├── data/
│   ├── models/
│   ├── ├── email.js
│   ├── ├── index.js
│   ├── ├── Personal.js
│   ├── router/
│   ├── ├── Personal.js
│   ├── index.js
├── package.json
├── .gitignore
├── README.md

前端功能开发

页面开发

  • 创建登录页面:views/Login/LoginView.vue

微信截图_20241019135816.png

  • 创建注册页面:views/Register/RegisterView.vue

微信截图_20241019135821.png

  • 创建密码修改页面:views/Forgot/ForgotView.vue

微信截图_20241019135834.png

路由配置

引入创建好的页面路径:router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('@/views/Home/HomeView.vue')
    },
    {
      path: '/forgot',
      name: 'Forgot',
      component: () => import('@/views/Forgot/ForgotView.vue')
    },
    {
      path: '/register',
      name: 'Register',
      component: () => import('@/views/Register/RegisterView.vue')
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/Login/LoginView.vue')
    }
  ]
})
​
export default router

配置路由守卫,仅允许在已登录下访问首页

router.beforeEach((to) => {
  const token = localStorage.getItem('token')
  if (
    !token &&
    (to.name !== 'Login' && to.name !== 'Register' && to.name !== 'Forgot')
  ) {
    return { name: 'Login' }
  }
})

接口封装

创建一个index文件配置请求响应拦截:api/index.ts

import axios, { type AxiosInstance, type InternalAxiosRequestConfig, type AxiosResponse } from 'axios';
const baseURL: string = 'api'  // 基础 URL
const request: AxiosInstance = axios.create({
  baseURL,
  timeout: 5000, // 超时时间
});
​
// 请求拦截
request.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token: string | null = localStorage.getItem('token')
    config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);
​
// 响应拦截
request.interceptors.response.use(
  (response: AxiosResponse) => {
    const res = response.data;
    if (res.code !== 200) {
      return Promise.reject(res.message);
    } else {
      return res;
    }
  },
  (error) => {
    return Promise.reject(error);
  }
);
​
export default request;
​

配置跨域代理:vite.config.ts

server: {
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:4000/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, '')
      }
    }
}

后端功能开发

路由配置

const express = require('express')
const router = express.Router()
const url = require('url')
// 示例:账号注册
router.post("/register", function (req, res) {
    let body = ''
    req.on('data', (thunk) => {
        body += thunk
    })
    req.on('end', () => {
        const data = JSON.parse(body)
        if(data.code == code) {
            res.send(JSON.stringify({
                code: 200,
                message: "注册成功"
            }))
        }else {
            res.send(JSON.stringify({
                code: 400,
                message: "注册失败"
            }))
        }
​
    })
})
// 示例:检查账号注册情况
router.get("/checkAccount", function (req, res) {
    let obj = url.parse(req.url, true).query
    if(obj.flag) {
        res.send(JSON.stringify({
            code: 400,
            message: '当前账号已注册'
        }))
    }else {
        res.send(JSON.stringify({
            code: 200,
            message: '当前账号未注册'
        }))
    }
})

链接数据库

当前仅创建两个字段用于保存:email(邮箱),password(密码)

const sqlite = require('sqlite3').verbose()
class Personal {
    constructor() {
        this.db = new sqlite.Database('data/data.db', () => {
            console.log('数据库打开成功')
            this.db.run('CREATE TABLE IF NOT EXISTS personal (id INTEGER PRIMARY KEY AUTOINCREMENT, email VARCHAR, password VARCHAR)');
        })
    }
    insert_personal(param) {
        let add = this.db.prepare("INSERT OR REPLACE INTO personal (email, password) VALUES (?,?)");
        add.run(param.email, param.password)
        add.finalize()
        console.log('数据插入成功')
    }
    update_personal(param) {
        let update = this.db.prepare("UPDATE personal set password=? where email=?")
        update.run(param.password, param.email)
        update.finalize()
        console.log('数据更新成功')
    }
    select_personal() {
        return new Promise((resolve) => {
            this.db.all("SELECT * FROM personal", (err, row) => {
                resolve(row)
            })
        })
    }
}

配置邮箱

当前项目使用的是网易邮箱,用于发送邮件信息

企业微信截图_17293227372195.png

  • 点击开启IMAP/SMTP服务,根据操作发送指定信息,会得到一个授权密码(注:授权密码仅显示一次,需要保存下来)。

微信截图_20241019152737.png

const nodemailer = require('nodemailer');
​
let config = {
    port: {
        host: "smtp.163.com", // 网易邮箱默认服务地址,无需修改
        port: 465, // 默认端口号
        secure: true,
        auth: {
            user: '你的网易邮箱',
            pass: '授权码'
        }
    },
    mailOptions: { // 邮件信息配置
        from: '你的网易邮箱',
        to: '接收方的邮箱',
        subject: 'Your verification code is: ${code}',
        text: ''
    }
}
​
function sendCodeToEmail(to, code) {
    return new Promise((resolve, reject) => {
        const transporter = nodemailer.createTransport(config.port);
        transporter.sendMail(config.mailOptions, (error, info) => {
            if(error) {
                reject(error)
            }else {
                resolve(info)
            }
        });
    })
}

项目运行

前端部分

npm run dev

后端部分

npm run start

后续

以上便是登录模块的完整开发流程,这一版仅是简单的初版设计,后面会不断优化和完善,让这个模块更加稳定。页面及交互的代码涉及篇幅较长,这里没有全部列出,可以根据情况自行设计。

下一章:工作台模块及个人信息模块