图书管理系统练习项目源码-前后端分离-使用node.js来做后端开发

发布于:2025-06-30 ⋅ 阅读:(16) ⋅ 点赞:(0)

前端学习了这么久了,node.js 也有了一定的了解,知道使用node也可以来开发后端,今天给大家分享 使用node 来做后端,vue来写前端,做一个简单的图书管理系统。我们在刚开始学习编程的时候,需要自己写大量的项目来练习自己的编程技能,在开发中你会遇到各种各样的编程问题,这个时候 就可以根据自己学习到的编程知识来解决开发中遇到的问题,这样我们就会慢慢的掌握一门编程语言。
今天分享的就是我最近写的一个图书管理系统。
使用技术:
Node.js:后端使用Node.js平台,版本要求16.20以上,基于其高性能和跨平台特性,能够轻松支持大规模的API请求。

Express框架:简洁高效的后端框架,帮助快速搭建API服务。

MySQL 5.7:作为关系型数据库管理系统,MySQL用于存储用户和图书信息,并支持CRUD操作。

Vue2:前端使用Vue2框架,配合Element UI组件库,提供响应式页面和现代化用户界面。

Element UI:帮助实现简洁且功能丰富的UI设计,极大提高了前端开发效率。

系统功能
用户管理:可以新增、编辑、删除用户信息。

图书管理:添加、修改、删除图书,并能够查看图书列表。

借阅管理:记录图书借阅、归还情况。

数据展示:通过前端页面展示系统中的图书、用户信息,提供了简洁、易用的界面。
部分页面效果展示:
在这里插入图片描述
在这里插入图片描述
目录结构:
在这里插入图片描述
部分前端代码:

<template>
  <div class="app-container">
    <!-- 搜索和操作栏 -->
    <div class="filter-container">
      <el-input
        v-model="listQuery.name"
        placeholder="请输入图书名称"
        style="width: 200px; margin-right: 10px"
        class="filter-item"
        @keyup.enter.native="handleFilter"
      />
      <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
      <el-button type="primary" @click="handleCreate" style="margin-left: 10px">新增图书</el-button>
    </div>

    <!-- 数据表格 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="加载中..."
      border
      fit
      highlight-current-row
      style="margin-top: 20px;"
    >
      <el-table-column label="图书编号" prop="book_no" align="center" />
      <el-table-column label="图书名称" prop="name" align="center" />
      <el-table-column label="分类" prop="category_name" align="center" />
      <el-table-column label="作者" prop="author" align="center" />
      <el-table-column label="使用状态" align="center">
        <template slot-scope="{row}">
          <el-tag :type="row.use_status === 0 ? 'info' : row.use_status === 1 ? 'success' : 'warning'">
            {{ row.use_status === 0 ? '在书架' : row.use_status === 1 ? '已买' : '已借出' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="是否二手" align="center">
        <template slot-scope="{row}">
          <el-tag :type="row.is_second_hand === 1 ? 'warning' : 'info'">
            {{ row.is_second_hand === 1 ? '是' : '否' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="状态" align="center">
        <template slot-scope="{row}">
          <el-tag :type="row.status === 1 ? 'success' : 'danger'"  style="cursor: pointer">
            {{ row.status === 1 ? '正常' : '维护' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="备注" prop="remark" align="center" show-overflow-tooltip />
      <el-table-column label="创建时间" prop="created_at" align="center" />
      <el-table-column label="操作" align="center" width="160" fixed="right">
        <template slot-scope="{row}">
          <el-button type="primary" size="mini" @click="handleUpdate(row)">编辑</el-button>
          <el-button type="danger" size="mini" @click="handleDelete(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 新增/编辑对话框 -->
    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px">
      <el-form
        ref="dataForm"
        :model="temp"
        :rules="rules"
        label-position="left"
        label-width="100px"
        style="margin-left: 50px; margin-right: 50px"
      >
        <el-form-item label="图书编号" prop="book_no">
          <el-input v-model="temp.book_no" placeholder="请输入图书编号" />
        </el-form-item>
        <el-form-item label="图书名称" prop="name">
          <el-input v-model="temp.name" placeholder="请输入图书名称" />
        </el-form-item>
        <el-form-item label="图书分类" prop="category_id">
          <el-select v-model="temp.category_id" placeholder="请选择图书分类" style="width: 100%">
            <el-option
              v-for="item in categories"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="作者" prop="author">
          <el-input v-model="temp.author" placeholder="请输入作者" />
        </el-form-item>
        <el-form-item label="出版日期" prop="publish_date">
          <el-date-picker
            v-model="temp.publish_date"
            type="datetime"
            placeholder="请选择出版日期"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="定价" prop="price">
          <el-input-number
            v-model="temp.price"
            :precision="2"
            :step="0.1"
            :min="0"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="temp.status" placeholder="请选择状态" style="width: 100%">
            <el-option label="正常" :value="1" />
            <el-option label="维护" :value="0" />
          </el-select>
        </el-form-item>
        <el-form-item label="使用状态" prop="use_status">
          <el-select v-model="temp.use_status" placeholder="请选择使用状态" style="width: 100%">
            <el-option label="在书架" :value="0" />
            <el-option label="已买" :value="1" />
            <el-option label="已借出" :value="2" />
          </el-select>
        </el-form-item>
        <el-form-item label="是否二手" prop="is_second_hand">
          <el-select v-model="temp.is_second_hand" placeholder="请选择是否二手" style="width: 100%">
            <el-option label="否" :value="0" />
            <el-option label="是" :value="1" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input
            v-model="temp.remark"
            type="textarea"
            placeholder="请输入备注信息"
            :rows="3"
          />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitForm">确定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import {
  getBookList,
  createBook,
  updateBook,
  deleteBook,
  updateBookStatus
} from '@/api/book'
import { getCategoryList } from '@/api/category'

export default {
  name: 'BookList',
  data() {
    return {
      list: [], // 图书列表数据
      categories: [], // 分类列表数据
      listLoading: false, // 列表加载状态
      listQuery: { // 查询参数
        name: '' // 图书名称搜索关键词
      },
      dialogVisible: false, // 对话框显示状态
      dialogTitle: '', // 对话框标题
      temp: { // 临时数据对象
        id: undefined,
        book_no: '',
        name: '',
        category_id: undefined,
        author: '',
        publish_date: undefined,
        price: 0,
        status: 1,
        remark: '',
        use_status: 0,
        is_second_hand: 0
      },
      rules: { // 表单验证规则
        book_no: [{ required: true, message: '请输入图书编号', trigger: 'blur' }],
        name: [{ required: true, message: '请输入图书名称', trigger: 'blur' }],
        category_id: [{ required: true, message: '请选择图书分类', trigger: 'change' }],
        author: [{ required: true, message: '请输入作者', trigger: 'blur' }],
        publish_date: [{ required: true, message: '请选择出版日期', trigger: 'change' }],
        price: [{ required: true, message: '请输入定价', trigger: 'blur' }]
      }
    }
  },
  created() {
    this.getList()
    this.getCategories()
  },
  methods: {
    // 获取图书列表
    async getList() {
      try {
        this.listLoading = true
        const { data } = await getBookList(this.listQuery)
        this.list = data
      } catch (error) {
        console.error('获取图书列表失败:', error)
      } finally {
        this.listLoading = false
      }
    },

    // 处理搜索
    handleFilter() {
      this.getList()
    },

    // 获取分类列表
    async getCategories() {
      try {
        const { data } = await getCategoryList()
        this.categories = data
      } catch (error) {
        console.error('获取分类列表失败:', error)
      }
    },

    // 重置表单
    resetTemp() {
      this.temp = {
        id: undefined,
        book_no: '',
        name: '',
        category_id: undefined,
        author: '',
        publish_date: undefined,
        price: 0,
        status: 1,
        remark: '',
        use_status: 0,
        is_second_hand: 0
      }
    },

    // 打开新增对话框
    handleCreate() {
      this.resetTemp()
      this.dialogTitle = '新增图书'
      this.dialogVisible = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },

    // 打开编辑对话框
    handleUpdate(row) {
      this.temp = Object.assign({}, row)
      this.dialogTitle = '编辑图书'
      this.dialogVisible = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },

    // 提交表单
    submitForm() {
      this.$refs['dataForm'].validate(async (valid) => {
        if (valid) {
          try {
            if (this.temp.id) {
              // 更新
              await updateBook(this.temp.id, this.temp)
              this.$message.success('更新成功')
            } else {
              // 新增
              await createBook(this.temp)
              this.$message.success('创建成功')
            }
            this.dialogVisible = false
            this.getList()
          } catch (error) {
            console.error('保存图书失败:', error)
          }
        }
      })
    },

    // 删除图书
    handleDelete(row) {
      this.$confirm('确认删除该图书吗?', '提示', {
        type: 'warning'
      }).then(async () => {
        try {
          await deleteBook(row.id)
          this.$message.success('删除成功')
          this.getList()
        } catch (error) {
          console.error('删除图书失败:', error)
        }
      }).catch(() => {})
    },

    // 更新状态
    async handleStatusChange(row) {
      try {
        await updateBookStatus(row.id, row.status)
        this.$message.success('状态更新成功')
      } catch (error) {
        console.error('更新状态失败:', error)
        // 恢复原状态
        row.status = row.status === 1 ? 0 : 1
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.filter-container {
  padding-bottom: 10px;
  .filter-item {
    margin-right: 10px;
  }
}
</style>

部分后端代码:

const BookModel = require('../models/book.model')
const Response = require('../utils/response')
const asyncHandler = require('../utils/asyncHandler')

class BookController {
  /**
   * 获取图书列表
   */
  getList = asyncHandler(async (req, res) => {
    const { name } = req.query
    const books = await BookModel.getAll({ name })
    res.json(Response.success(books))
  })

  /**
   * 获取图书详情
   */
  getDetail = asyncHandler(async (req, res) => {
    const { id } = req.params
    const book = await BookModel.findById(id)
    
    if (!book) {
      return res.json(Response.error('图书不存在'))
    }

    res.json(Response.success(book))
  })

  /**
   * 创建图书
   */
  create = asyncHandler(async (req, res) => {
    const { book_no, name, category_id, author, publish_date, price, status, remark, use_status, is_second_hand } = req.body

    // 验证必填字段
    if (!book_no) {
      return res.json(Response.error('图书编号不能为空'))
    }
    if (!name) {
      return res.json(Response.error('图书名称不能为空'))
    }
    if (!category_id) {
      return res.json(Response.error('图书分类不能为空'))
    }
    if (!author) {
      return res.json(Response.error('作者不能为空'))
    }
    if (!publish_date) {
      return res.json(Response.error('出版日期不能为空'))
    }
    if (!price) {
      return res.json(Response.error('定价不能为空'))
    }

    const id = await BookModel.create({ 
      book_no, 
      name, 
      category_id, 
      author, 
      publish_date, 
      price, 
      status, 
      remark,
      use_status,
      is_second_hand
    })
    const book = await BookModel.findById(id)
    res.json(Response.success(book, '创建成功'))
  })

  /**
   * 更新图书
   */
  update = asyncHandler(async (req, res) => {
    const { id } = req.params
    const { book_no, name, category_id, author, publish_date, price, status, remark, use_status, is_second_hand } = req.body

    const book = await BookModel.update(id, { 
      book_no, 
      name, 
      category_id, 
      author, 
      publish_date, 
      price, 
      status, 
      remark,
      use_status,
      is_second_hand
    })
    res.json(Response.success(book, '更新成功'))
  })

  /**
   * 删除图书
   */
  delete = asyncHandler(async (req, res) => {
    const { id } = req.params
    await BookModel.delete(id)
    res.json(Response.success(null, '删除成功'))
  })

  /**
   * 更新图书状态
   */
  updateStatus = asyncHandler(async (req, res) => {
    const { id } = req.params
    const { status } = req.body

    if (status === undefined) {
      return res.json(Response.error('状态不能为空'))
    }

    const book = await BookModel.updateStatus(id, status)
    res.json(Response.success(book, '状态更新成功'))
  })
}

module.exports = new BookController()

代码量还是很多的,毕竟是一个管理系统,由于文章字数,这里就不一一复制粘贴代码了,如果你需要完整的项目源码,可以去下方网站了解。
https://wwwoop.com/home/Index/projectInfo?goodsId=97&typeParam=1&subKey=0