【AI编程工具】快速搭建图书管理系统

发布于:2025-09-06 ⋅ 阅读:(18) ⋅ 点赞:(0)
大家好,我是工藤学编程 🦉 一个正在努力学习的小博主,期待你的关注
实战代码系列最新文章😉 C++实现图书管理系统(Qt C++ GUI界面版)
SpringBoot实战系列🐷 【SpringBoot实战系列】SpringBoot3.X 整合 MinIO 存储原生方案
分库分表 分库分表之实战-sharding-JDBC分库分表执行流程原理剖析
消息队列 深入浅出 RabbitMQ-RabbitMQ消息确认机制(ACK)

基于Vue 3 + JSON Server 实现轻量图书管理系统

AI提示词如下:

我要实现一个图书管理系统,请你用vue相关代码,以及生成json文件作为接口返回内容,功能包括注册、登录,分类,查询即可

在前端学习中,“图书管理系统”是覆盖“用户认证+数据CRUD”的经典案例。本文将用Vue 3(Composition API)+ JSON Server搭建系统,实现注册、登录、图书分类、模糊查询四大核心功能,无需后端开发,10分钟即可跑通完整流程。

一、技术栈与环境准备

1. 技术栈选型

  • 前端核心:Vue 3(Vite构建,Composition API)
  • 路由管理:Vue Router 4(控制页面权限)
  • 网络请求:Axios(处理接口调用)
  • 模拟后端:JSON Server(基于JSON文件提供RESTful接口)
  • 样式辅助:Tailwind CSS(快速实现简洁界面,可选)

2. 环境搭建步骤

步骤1:创建Vue项目

打开终端,执行以下命令创建Vite+Vue 3项目:

# 创建项目(项目名:book-management-system)
npm create vite@latest book-management-system -- --template vue

# 进入项目目录
cd book-management-system

# 安装依赖
npm install

# 安装额外依赖(路由、axios)
npm install vue-router@4 axios
步骤2:安装JSON Server

JSON Server可将JSON文件转为可访问的API接口,全局安装:

npm install -g json-server

二、JSON Server 模拟后端接口

1. 创建接口数据文件

在项目根目录新建db.json文件(模拟数据库),包含用户表(users)图书表(books) ,初始数据如下:

{
  list: [
        {
            category:"literature",
            bookImg:"http://t14.baidu.com/it/u=2285971984,1142952378&fm=224&app=112&f=JPEG?w=500&h=500",  
            id:1,
            author:"Charlotte Brontë",
            price:"36",
            bookName:"Jane Eyre"
        },
         {
            category:"literature",
            bookImg:"https://pic.rmb.bdstatic.com/acf7204f9c18b91bd8e3ce3d09961118.jpeg",  
            id:2,
            author:"Victor Hugo",
            price:"33",
            bookName:"The Hunchback of Notre-Dame"
        },
        {
            category:"education",
            bookImg:"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.alicdn.com%2Fbao%2Fuploaded%2Fi1%2F1899623276%2FTB1IRy9hnlYBeNjSszcXXbwhFXa_%21%210-item_pic.jpg&refer=http%3A%2F%2Fimg.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1731158616&t=f5b9cf04e7f605e74c0b0cb0c2e8896e",  
            id:3,
            author:"Plato",
            price:"78",
            bookName:"The Republic"
        },
        {
            category:"life",
            bookImg:"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01lwQysa2L7SnTLGSPy_%21%214038439645-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1731159003&t=fb0a6f17e841bf5b7b4ebfa143f14d17",  
            id:4,
            author:"Viktor E. Frankl",
            price:"75",
            bookName:"Man's Search for Meaning"
        }
    ]

2. 启动JSON Server

在终端执行命令,启动接口服务(端口3000,避免与Vue项目冲突):

json-server --watch db.json --port 3000

启动成功后,可通过以下地址访问接口:

  • 查看所有用户:http://localhost:3000/users
  • 查看所有图书:http://localhost:3000/books
  • 新增用户(注册):POST http://localhost:3000/users
  • 图书查询:GET http://localhost:3000/books?q=关键词(模糊查询)

三、Vue前端核心功能实现

1. 项目目录结构

先梳理前端文件:
在这里插入图片描述

2. 核心配置实现

(1)Axios封装(处理请求拦截、基础路径)

新建src/utils/request.js,统一管理接口请求:

import axios from 'axios'

// 创建Axios实例
const request = axios.create({
  baseURL: 'http://localhost:3000', // JSON Server地址
  timeout: 5000
})

// 请求拦截器(可选:添加token)
request.interceptors.request.use(
  config => {
    // 从localStorage获取token(登录后存储)
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => Promise.reject(error)
)

// 响应拦截器(统一处理错误)
request.interceptors.response.use(
  response => response.data,
  error => {
    alert('接口请求失败:' + error.message)
    return Promise.reject(error)
  }
)

export default request
(2)路由配置(含登录权限控制)

新建src/router/index.js,区分“公开页面”和“需登录页面”:

import { createRouter, createWebHistory } from 'vue-router'
import Login from '../views/Login.vue'
import Register from '../views/Register.vue'
import BookList from '../views/BookList.vue'

// 路由规则
const routes = [
  { path: '/', redirect: '/login' }, // 根路径重定向到登录页
  { path: '/login', component: Login, meta: { isPublic: true } }, // 公开页面
  { path: '/register', component: Register, meta: { isPublic: true } }, // 公开页面
  { path: '/book-list', component: BookList } // 需登录页面
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由守卫(未登录拦截)
router.beforeEach((to, from, next) => {
  // 非公开页面且无token,跳转到登录页
  if (!to.meta.isPublic && !localStorage.getItem('token')) {
    next('/login')
  } else {
    next()
  }
})

export default router
(3)入口文件配置(挂载路由)

修改src/main.js,引入路由和Axios:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import request from './utils/request'

const app = createApp(App)

// 全局挂载Axios(方便组件内通过this.$request调用)
app.config.globalProperties.$request = request

app.use(router).mount('#app')

3. 核心页面功能实现

(1)注册页面(Register.vue)

实现“用户名+密码”注册,调用JSON Server的POST接口新增用户:

<template>
  <div class="register">
    <div class="backgroundImage"></div>
    <div class="register_container">
      <h1>Book Management System</h1>
      <!-- Registration form, binding user information and validation rules -->
      <el-form class="register_form" :model="userInfo" :rules="rules" @keyup.enter="registerIn" ref="ref_form">
        <el-form-item prop="userName">
          <el-input placeholder="Please enter your username" v-model.trim="userInfo.userName">
            <template #prepend>
              <el-icon>
                <avatar />
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          
          <el-input placeholder="Please enter your password" show-password v-model.trim="userInfo.password">
            <template #prepend>
              <el-icon>
                <key/>
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- Registration button, triggers the registration process when clicked -->
        <el-button type="primary" class="register_submit" @click="registerIn">Register</el-button>
        <!-- Login link, redirects to the login page when clicked -->
        <div class="login_register" @click="toLogin">Already have an account? Go to login</div>
      </el-form>
    </div>
  </div>
</template>

<script setup>
import router from "@/router";
import { ElMessage } from "element-plus";
import { reactive, ref } from "vue";
// Reference to the form
const ref_form = ref(null);
// Reactive user information object
const userInfo = reactive({
  userName: '',
  password: ''
});
// Form validation rules
const rules = {
  userName: [{ required: true, trigger: 'blur', message: 'Username cannot be empty' }],
  password: [{ required: true, trigger: 'blur', message: 'Password cannot be empty' }],
};
// Registration method, validates the form and calls getRegisterInfo method
const registerIn = () => {
  ref_form.value.validate((val) => {
    if (val) {
      getRegisterInfo();
    }
  });
};
// Get registration information method, simulates successful registration and redirects to the login page
const getRegisterInfo = () => {
  ElMessage({
    message: 'Registration successful',
    type: 'success'
  });
  router.push('/login');
};
// Method to redirect to the login page
const toLogin = () => {
  router.push('/login');
};
</script>
<style lang="less" scoped>
@-webkit-keyframes fedenum {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
.register {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  position: relative; 
}

.backgroundImage {
  height: 100%;
  width: 100%; 
  background-image: url("../assets/bj.jpg");
  background-repeat: no-repeat;
  background-size: cover; 
  background-position: center; 
  position: absolute; 
  top: 0;
  left: 0;
  z-index: 0; 
}

.register_container {
  animation: fadenum 1s ease;
  position: relative; 
  z-index: 1;
  width: 400px;
  min-height: 273px;
  text-align: center;
  background-color: #fff;
  border-radius: 10px;
  padding: 20px;
}
el-icon {
  height: 30px !important;
}

h1 {
  font-size: 24px;
  transition: all 1s;
  font-weight: bold;
  margin-bottom: 36px;
}
.el-card {
  border-radius: 20px;
}
.register_form {
  .register_submit {
    width: 100%;
    height: 50px;
    border-radius: 20px;
    margin-bottom: 10px;
  }
  .login_register {
    width: 11rem;
    font-size: 0.875rem;
    margin: 0 auto;
  }
  :deep(.el-input-group__prepend) {
    padding: 0 10px;
  }
}
</style>

(2)登录页面(Login.vue)

验证用户名密码,登录成功后存储token(模拟JWT)并跳转图书列表:

<template>
  <div class="login">
    <div class="backgroundImage"></div>
    <div class="login_container">
      <h1>Book Management System</h1>
      <!-- Login form, binding user information and validation rules -->
      <el-form class="login_form" :model="userInfo" :rules="rules" @keyup.enter="loginIn" ref="ref_form">
        <el-form-item prop="userName">
          <el-input placeholder="Please enter your username" v-model.trim="userInfo.userName">
            <template #prepend>
              <el-icon>
                <avatar />
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input placeholder="Please enter your password" show-password v-model.trim="userInfo.password">
            <template #prepend>
              <el-icon>
                <key/>
              </el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- Login button, triggers login operation when clicked -->
        <el-button type="primary" class="login_submit" @click="loginIn">Login</el-button>
        <!-- Register link, redirects to register page when clicked -->
        <div class="login_register" @click="toRegister">Register</div>
      </el-form>
    </div>
  </div>
</template>

<script setup>
import { userStore } from "@/common/js/store";
import router from "@/router";
import { ElMessage } from "element-plus";
import { reactive, ref } from "vue";
// Reference to the form
const ref_form = ref(null)
// Reactive user information object
const userInfo = reactive({
  userName: '',
  password: ''
})
// Form validation rules
const rules = {
  userName: [{ required: true, trigger: 'blur', message: 'Username cannot be empty' }],
  password: [{ required: true, trigger: 'blur', message: 'Password cannot be empty' }],
}
// Login method, validates the form and calls getUserInfo method
const loginIn = () => {
  ref_form.value.validate((val) => {
    if (val) {
      getUserInfo()
    }
  })
}
// Get user information method, simulates successful login and redirects to the home page
const getUserInfo = () => {
  userStore.userName = userInfo.userName;
  localStorage.setItem('token', '100') // Simulate setting a token
  ElMessage({
    message: 'Login successful',
    type: 'success'
  })
  router.push('/home') // After successful login, redirect to the home page
}
// Method to redirect to the register page
const toRegister = () => {
  router.push('/register')
}
</script>
<style lang="less" scoped>
@-webkit-keyframes fedenum {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh; 
  position: relative; 
}

.backgroundImage {
  height: 100%;
  width: 100%; 
  background-image: url("../assets/bj.jpg");
  background-repeat: no-repeat;
  background-size: cover; 
  background-position: center;
  position: absolute; 
  top: 0;
  left: 0;
  z-index: 0; 
}

.login_container {
  animation: fadenum 1s ease;
  position: relative; 
  z-index: 1;
  width: 400px;
  min-height: 273px;
  text-align: center;
  background-color: #fff;
  border-radius: 10px;
  padding: 20px;
}

el-icon {
  height: 30px !important;
}
h1 {
  font-size: 24px;
  transition: all 1s;
  font-weight: bold;
  margin-bottom: 36px;
}
.el-card {
  border-radius: 20px;
}
.login_form {
  .login_submit {
    width: 100%;
    height: 50px;
    border-radius: 20px;
    margin-bottom: 10px;
  }
  .login_register {
    width: 6.25rem;
    font-size: 0.875rem;
    margin: 0 auto;
  }
  :deep(.el-input-group__prepend) {
    padding: 0 10px;
  }
}
</style>

(3)图书列表页(Home.vue)

实现图书分类筛选模糊查询,核心功能:

  • 加载所有图书
  • 按“前端开发/后端开发/计算机基础”分类筛选
  • 按书名/作者模糊查询
<template>
  <div class="home">
    <!-- Use Element UI's el-container component to create a container for layout -->
    <el-container>
      <!-- Use el-aside component to create a sidebar -->
      <el-aside width="200px">
        <!-- Include the Aside component to display the sidebar content -->
        <!-- :handleCollapse and :isCollapse are used to control the collapse state of the sidebar -->
        <!-- @updateCategory listens for the updateCategory event emitted by the child component to update the current category -->
        <Aside :handleCollapse='handleCollapse' :isCollapse='isCollapse' @updateCategory="handleCategoryChange"/>
      </el-aside>
      <el-container>
        <!-- Use el-header component to create the header area -->
        <el-header>
          <!-- Include the Header component to display the header content -->
          <!-- :handleCollapse and :isCollapse are used to control the collapse state of the header -->
          <Header :handleCollapse='handleCollapse' :isCollapse='isCollapse'/>
        </el-header>
        <el-main>
          <!-- Use el-main component to create the main content area -->
          <!-- Include the Main component to display the main content -->
          <!-- :category is used to pass the current category to the Main component -->
          <Main :category="currentCategory"/>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup>
import Header from '@/components/Header.vue'
import Aside from '@/components/Aside.vue'
import Main from '@/components/Main.vue'
import { userStore } from "@/common/js/store";
import { ref } from 'vue'

// Define a reactive variable to control the collapse state of the sidebar and header
const isCollapse = ref(false)
// Define a reactive variable to store the currently selected category
const currentCategory = ref('')
// Define a method to toggle the collapse state of the sidebar and header
const handleCollapse = () => {
  isCollapse.value = !isCollapse.value
}
// Define a method to handle category changes
// When the Aside component emits an updateCategory event, this method updates the value of currentCategory
const handleCategoryChange = (category) => {
  currentCategory.value = category
}
</script>
<style lang="less" scoped>
.el-aside{
  width: auto;
  background-color: #544c64;
  overflow: hidden;
}
.el-container{
  height: 100vh;
}
.el-main{
  display: flex;
}
.el-header{
  background-color: #fff;
}
</style>

(4)导航栏组件(Navbar.vue)

实现“用户名显示”和“退出登录”功能:

<template>
  <!-- Create a vertical navigation menu with custom active text color, background color, and text color -->
   <!-- Whether to collapse, bound to the isCollapse variable -->
  <el-menu
    class="el-menu-vertical-demo"
    active-text-color="#15df84"
    background-color="#545c64"
    text-color="#fff"
    :collapse="isCollapse"
  >
    <!-- Define a library icon area containing an icon and text -->
    <div class="library-icon">
      <img src="../assets/library-icon.png"> <!-- Library icon -->
      <p>Library Management System</p>
    </div>
    <!-- Define a submenu for book management -->
    <el-sub-menu index="1">
      <template #title>
        <el-icon><document /></el-icon>
        <span>Book Management</span>
      </template>
      <!-- Define three menu items that trigger the updateCategory method and pass category parameters when clicked -->
      <el-menu-item index="1-1" @click="updateCategory('literature')">Literature</el-menu-item>
      <el-menu-item index="1-2" @click="updateCategory('education')">Education</el-menu-item>
      <el-menu-item index="1-3" @click="updateCategory('life')">Life</el-menu-item>
    </el-sub-menu>
  </el-menu>
</template>
<script setup>
// Receive the isCollapse property from the parent component
const { isCollapse } = defineProps(['isCollapse'])
// Define an event emitter to send events to the parent component
const emit = defineEmits(['updateCategory'])

// Method to update category, triggers the updateCategory event and passes category parameters
const updateCategory = (category) => {
  emit('updateCategory', category)
}
</script>
<style lang = 'less' scoped>
.el-radio-button_inner{
    padding: 0;
}
.el-menu--collapse{
    border: none;
}
.el-menu:not(.el-menu--collapse){
    width: 200px;
    min-height: 800px;
    border: none;
}
.library-icon{
    display: flex;
    align-items: center;
    font-weight: 600;
    height: 60px;
    background-color: #fff;
    white-space: nowrap;
    padding-left: 15px;
}
img{
    width: 45px;
    height: 40px;
    margin-right: 10px;
}
p{
  font-size: 10px;
  font-weight: bold;
}
</style>

四、项目运行与功能演示

1. 启动项目

步骤1:启动JSON Server(后端接口)
json-server --watch db.json --port 3000
步骤2:启动Vue项目(前端)

打开新终端,进入项目目录:

npm run dev

Vue项目默认启动在http://localhost:5173

2. 功能演示流程

  1. 注册:访问http://localhost:5173/register,输入用户名(如test123)和密码(如123456),注册成功后跳登录页。
    在这里插入图片描述

  2. 登录:输入刚注册的账号密码,登录成功后跳图书列表页。
    在这里插入图片描述

  3. 图书分类:点击“前端开发”“后端开发”等按钮,可筛选对应分类的图书。
    在这里插入图片描述

  4. 模糊查询:在搜索框输入关键词(如Jan),实时筛选图书。
    在这里插入图片描述

  5. 退出登录:点击导航栏“退出登录”,清除缓存并返回登录页。

在这里插入图片描述

觉得有用请点赞收藏!
如果有相关问题,欢迎评论区留言讨论~


网站公告

今日签到

点亮在社区的每一天
去签到