【实战】 Vue 3、Anything LLM + DeepSeek本地化项目(一)

487 阅读2分钟

本次目标完成一个简易的授权登录:

  1. 前端使用Vue3VueRouterPainaaxiosElement Plush
  2. 服务端使用Node+Express做简易的JWT鉴权

前端登录页面搭建

简易登录界面实现

效果图 image.png 代码实例

在项目的src/views/Login下新建一个index.vue来实现组件的内容

<template>
  <div class="login-container">
    <el-card class="login-card">
      <el-form
        ref="loginForm"
        :model="form"
        :rules="rules"
        label-position="top"
        class="login-form"
      >
        <el-form-item label="用户名" prop="username">
          <el-input
            v-model="form.username"
            prefix-icon="el-icon-user"
            placeholder="请输入用户名"
          ></el-input>
        </el-form-item>

        <el-form-item label="密码" prop="password">
          <el-input
            v-model="form.password"
            type="password"
            prefix-icon="el-icon-lock"
            placeholder="请输入密码"
            show-password
          ></el-input>
        </el-form-item>

        <el-form-item>
          <el-button
            type="primary"
            class="login-button"
            @click="submitForm"
            :loading="loading"
          >
            登录
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script setup>
import axios from "@/axios";
import { ElMessage } from "element-plus";
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { useAuthStore } from "@/store/auth";
const router = useRouter();

const form = reactive({
  username: "",
  password: "",
});

const rules = {
  username: [
    { required: true, message: "请输入用户名", trigger: "blur" },
    { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },
  ],
  password: [
    { required: true, message: "请输入密码", trigger: "blur" },
    { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
  ],
};

const loginForm = ref(null);
const loading = ref(false);

const submitForm = () => {
  loginForm.value.validate((valid) => {
    if (valid) {
      loading.value = true;
      axios.post("/login", form).then(({ token }) => {
        ElMessage.success("登录成功");
        loading.value = false;
        useAuthStore().login(token);
        router.push("/");
      });
    } else {
      ElMessage.error("表单验证失败");
    }
  });
};
</script>

<style scoped>
/* 全屏背景色 */
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw; /* 确保宽度铺满全屏 */
  background-color: #f5f7fa; /* 淡色背景 */
  overflow: hidden;
}

/* 登录卡片样式 */
.login-card {
  width: 400px;
  padding: 30px;
  border-radius: 10px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  background-color: #ffffff; /* 卡片背景色 */
}

/* 登录表单样式 */
.login-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* 登录按钮样式 */
.login-button {
  width: 100%;
}
</style>

src/下新建一个axios.ts的脚本文件来做简易的axios的封装和请求、响应拦截内容

// src/axios.js
import axios from 'axios';

const instance = axios.create({
  baseURL: '/api', // 设置基础 URL
  timeout: 5000, // 设置超时时间
});

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么,例如添加 Token
    config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`;
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么
    return response.data;
  },
  (error) => {
    // 对响应错误做点什么
    if (error.response.status === 401) {
      // 处理 401 未授权错误
      console.error('未授权,请重新登录');
    }
    return Promise.reject(error);
  }
);

export default instance;

src/store新建auth.ts来存放登录相关的逻辑处理(项目中不存在store文件夹自行创建),这里会将获取到的token信息临时存放到sessionStorage中,后期会调整

import { defineStore } from "pinia";
interface State {
  token: string | null;
  isLoggedIn: boolean;
}
export const useAuthStore = defineStore("auth", {
  state: (): State => ({
    token: sessionStorage.getItem("token"),
    isLoggedIn: !!sessionStorage.getItem("token"),
  }),
  actions: {
    login(token: string) {
      this.token = token;
      this.isLoggedIn = true;
      sessionStorage.setItem("token", token);
    },
    logout() {
      this.token = null;
      this.isLoggedIn = false;
      sessionStorage.removeItem("token");
    }
  },
});

vite代码配置

vite.config.ts中配置代理来保证登录接口的调用

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src', // 确保这里的路径是正确的
    },
  },
  server: {
    port: 8080,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
})

服务端搭建与实现

项目主要练习的是Vue3的相关内容所以服务端的搭建相对简单,能支持正常授权登录即可

  1. 服务端监听了3000端口,如果冲突可自行修改,修改后需要同步调整前端代理的端口,否则会导致登录接口调用不通
  2. 因目前没有数据库支撑,为了能完成认证签信息签发,系统可以正常登录,代码中预制了adminpassword123作为默认的用户名和密码,后续接入数据库后会将用户信息持久化到数据库。

新建index.js文件来搭建一个简易的node服务支持登录、注册和登出等需求,具体代码如下:

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
require('dotenv').config();
const { JWT_SECRET } = process.env;

const app = express();
const port = 3000;

// 中间件
app.use(bodyParser.json());

// 模拟用户数据
const users = [
  {
    id: 1,
    username: 'admin',
    password: bcrypt.hashSync('password123', 10) // 使用 bcrypt 加密密码
  }
];

// 注册接口
app.post('/register', async (req, res) => {
  const { username, password } = req.body;
  const hashedPassword = bcrypt.hashSync(password, 10);

  const newUser = {
    id: users.length + 1,
    username,
    password: hashedPassword
  };

  users.push(newUser);
  res.send({ message: 'User registered successfully' });
});

// 登录接口
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username);

  if (!user || !bcrypt.compareSync(password, user.password)) {
    return res.status(401).send({ message: 'Invalid credentials' });
  }

  // 生成 JWT
  const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '1h' });

  res.send({ message: 'Login successful', token });
});

// 验证 JWT 的中间件
const authenticateToken = (req, res, next) => {
  const token = req.headers['authorization'];

  if (!token) {
    return res.status(401).send({ message: 'Unauthorized' });
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).send({ message: 'Forbidden' });
    }

    req.user = user;
    next();
  });
};

// 获取用户信息接口
app.get('/profile', authenticateToken, (req, res) => {
  res.send({ message: 'Welcome to your profile', user: req.user.username });
});

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});

至此,已经成功的搭建了一套简易的登录页面,如有问题欢迎留言讨论,共同学习~