大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Python的图书馆(图书借阅)管理系统(FastAPI+Vue3)

项目介绍
随着信息技术的高速发展和高校、公共图书馆藏书规模的不断扩大,传统依靠人工登记、纸质卡片管理图书借阅的方式已经难以满足现代图书馆高效、准确管理的需求。人工管理方式不仅工作量大、容易出错,而且查询统计困难、借阅信息难以及时更新,已经成为制约图书馆服务质量提升的重要因素。因此,设计并实现一套功能完善、操作便捷、运行稳定的图书馆(图书借阅)管理系统具有重要的现实意义。
本文基于 Python 语言,采用 FastAPI 作为后端 Web 框架、Vue3 作为前端框架、MySQL 作为后台数据库,设计并实现了一套前后端分离的图书馆图书借阅管理系统。系统后端使用 FastAPI 构建 RESTful API 接口,借助 SQLAlchemy 进行对象关系映射、Pydantic 完成数据校验、JWT 实现登录鉴权;前端使用 Vue3 + Vite + Element Plus + Pinia + Axios 技术栈构建单页应用,实现了美观友好的用户交互界面。
系统主要包括系统管理、图书管理、读者管理、借阅管理以及统计查询等功能模块,实现了管理员登录、图书与图书分类的增删改查、读者信息维护、图书借阅与归还、逾期罚款计算以及借阅数据统计等核心业务功能。论文按照软件工程的思想,依次完成了系统的需求分析、总体设计、数据库设计、详细实现与系统测试。测试结果表明,本系统功能完整、界面友好、运行稳定,能够有效提高图书馆的管理效率,具有较好的实用价值。
源码下载
链接:https://pan.baidu.com/s/1DGDj55EpfL-_y4hxIDmpHA?pwd=1234
提取码:1234
系统展示








核心代码
"""
图书分类路由
提供分类的增删改查接口
"""
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.category import Category
from app.schemas.category import CategoryCreate, CategoryUpdate
from app.core.security import require_admin, get_current_user
from app.core.response import success, fail, page_result
from app.core.exceptions import BusinessException
router = APIRouter(prefix="/category", tags=["图书分类"])
@router.get("/list", summary="分类列表")
def list_categories(
page: int = 1,
page_size: int = 10,
keyword: str = "",
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""分页查询图书分类列表,支持关键字搜索"""
query = db.query(Category)
if keyword:
query = query.filter(Category.name.like(f"%{keyword}%"))
total = query.count()
items = query.order_by(Category.sort.asc(), Category.id.asc()).offset((page - 1) * page_size).limit(page_size).all()
result = [{"id": c.id, "name": c.name, "sort": c.sort, "remark": c.remark} for c in items]
return page_result(result, total, page, page_size)
@router.get("/all", summary="全部分类(下拉用)")
def all_categories(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""获取全部分类,用于下拉选择"""
items = db.query(Category).order_by(Category.sort.asc()).all()
return success([{"id": c.id, "name": c.name} for c in items])
@router.post("/add", summary="新增分类")
def add_category(
req: CategoryCreate,
db: Session = Depends(get_db),
current_user: dict = Depends(require_admin),
):
"""新增图书分类(管理员)"""
exists = db.query(Category).filter(Category.name == req.name).first()
if exists:
return fail("分类名称已存在")
category = Category(name=req.name, sort=req.sort, remark=req.remark)
db.add(category)
db.commit()
return success(msg="添加成功")
@router.put("/update/{category_id}", summary="修改分类")
def update_category(
category_id: int,
req: CategoryUpdate,
db: Session = Depends(get_db),
current_user: dict = Depends(require_admin),
):
"""修改图书分类(管理员)"""
category = db.query(Category).filter(Category.id == category_id).first()
if not category:
raise BusinessException("分类不存在")
if req.name is not None:
category.name = req.name
if req.sort is not None:
category.sort = req.sort
if req.remark is not None:
category.remark = req.remark
db.commit()
return success(msg="修改成功")
@router.delete("/delete/{category_id}", summary="删除分类")
def delete_category(
category_id: int,
db: Session = Depends(get_db),
current_user: dict = Depends(require_admin),
):
"""删除图书分类(管理员)"""
category = db.query(Category).filter(Category.id == category_id).first()
if not category:
raise BusinessException("分类不存在")
db.delete(category)
db.commit()
return success(msg="删除成功")
<template>
<div class="page-container">
<div class="search-bar">
<el-select v-model="form.reader_id" placeholder="选择读者" filterable style="width:220px">
<el-option v-for="r in readers" :key="r.id" :label="`${r.real_name} (${r.card_no})`" :value="r.id" />
</el-select>
<el-select v-model="form.book_id" placeholder="选择图书" filterable style="width:280px">
<el-option v-for="b in books" :key="b.id" :label="`${b.title} (库存:${b.stock_count})`" :value="b.id" :disabled="b.stock_count <= 0" />
</el-select>
<el-button type="primary" @click="handleLend"><el-icon><Switch /></el-icon>办理借书</el-button>
</div>
<div class="table-card">
<h3 style="margin-bottom:16px;color:#303133">当前在借图书</h3>
<el-table :data="borrowingList" stripe border style="width:100%">
<el-table-column prop="reader_name" label="读者" min-width="100" />
<el-table-column prop="card_no" label="借书证号" min-width="130" />
<el-table-column prop="book_title" label="图书" min-width="180" show-overflow-tooltip />
<el-table-column prop="borrow_time" label="借阅时间" min-width="170" />
<el-table-column prop="due_time" label="应还时间" min-width="170" />
<el-table-column prop="renew_count" label="续借次数" min-width="90" />
<el-table-column prop="status_text" label="状态" min-width="80">
<template #default="{ row }">
<el-tag :type="row.status === 3 ? 'danger' : 'warning'" size="small">{{ row.status_text }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="fine" label="罚款(元)" min-width="90" />
<el-table-column label="操作" min-width="180" fixed="right">
<template #default="{ row }">
<el-button v-if="row.status !== 2" type="success" link @click="handleReturn(row)">还书</el-button>
<el-button v-if="row.status === 1" type="primary" link @click="handleRenew(row)">续借</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrap">
<el-pagination v-model:current-page="page" v-model:page-size="pageSize" :total="total"
layout="total, sizes, prev, pager, next" @change="loadBorrowing" />
</div>
</div>
</div>
</template>
<script setup>
/** 借阅管理页面 - 管理员办理借书/还书/续借 */
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getAllReaders, getBookList, getBorrowList, lendBook, returnBook, renewBook } from '@/api'
const readers = ref([])
const books = ref([])
const borrowingList = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const form = reactive({ reader_id: null, book_id: null })
async function loadReaders() {
const res = await getAllReaders()
readers.value = res.data
}
async function loadBooks() {
const res = await getBookList({ page: 1, page_size: 100 })
books.value = res.data.list
}
async function loadBorrowing() {
const res = await getBorrowList({ page: page.value, page_size: pageSize.value, active_only: 1 })
borrowingList.value = res.data.list
total.value = res.data.total
}
async function handleLend() {
if (!form.reader_id || !form.book_id) {
ElMessage.warning('请选择读者和图书')
return
}
await lendBook(form)
ElMessage.success('借书成功')
form.book_id = null
loadBooks()
loadBorrowing()
}
async function handleReturn(row) {
await ElMessageBox.confirm(`确定为「${row.reader_name}」办理还书吗?`, '还书确认', { type: 'info' })
const res = await returnBook(row.id)
ElMessage.success(res.msg)
loadBooks()
loadBorrowing()
}
async function handleRenew(row) {
await ElMessageBox.confirm(`确定为「${row.book_title}」办理续借吗?`, '续借确认', { type: 'info' })
await renewBook(row.id)
ElMessage.success('续借成功')
loadBorrowing()
}
onMounted(() => { loadReaders(); loadBooks(); loadBorrowing() })
</script>
43万+

被折叠的 条评论
为什么被折叠?



