大家好,我是Java1234_小锋老师,分享一套锋哥原创的微信小程序图书馆(图书借阅)管理系统(SpringBoot4+Vue3)
项目介绍
随着移动互联网与智慧校园建设的深入推进,传统图书馆在图书检索、借还办理、信息统计等方面存在效率偏低、服务触达不足等问题。本文设计并实现了一套“微信小程序图书馆(图书借阅)管理系统”,面向读者提供随时随地的图书浏览、自助借还、续借查询与个人中心服务,面向管理员提供基于 Web 的后台业务管理与数据统计分析能力。系统采用前后端分离架构:后端基于 Spring Boot 4 构建 RESTful 接口,数据访问层采用 MyBatis-Plus 实现高效 ORM 与分页查询;管理员前端基于 Vue 3、Vite、Element Plus 与 ECharts 实现可视化管理后台;读者端基于微信小程序原生框架实现轻量级移动应用。数据库采用 MySQL,库名为 db_library,表名统一以 t_ 开头。系统实现了读者注册登录、图书分类与图书信息管理、借阅/归还/续借、超期罚款计算、首页轮播运营、头像与个人资料维护、密码修改以及后台数据看板等核心功能。测试结果表明,系统功能完整、流程清晰、界面友好,能够满足高校或中小型图书馆数字化借阅管理的基本需求,对提升馆藏利用率与读者服务体验具有实际应用价值。
源码下载
链接: https://pan.baidu.com/s/1mbfl_uU883STx-MJRbgP1w?pwd=1234
提取码: 1234
系统展示
![]()
![]()
![]()
![]()
核心代码
package com.java1234.controller; import com.java1234.common.Result; import com.java1234.dto.BookRequest; import com.java1234.security.SecurityUtils; import com.java1234.service.BookService; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 图书管理控制器 */ @RestController @RequestMapping("/api/book") public class BookController { private final BookService bookService; /** * 构造图书控制器 * @param bookService 图书服务 */ public BookController(BookService bookService) { this.bookService = bookService; } /** * 分页查询图书列表 * @param page 页码 * @param pageSize 每页条数 * @param keyword 关键字 * @param categoryId 分类ID * @return 分页结果 */ @GetMapping("/list") public Result list(@RequestParam(defaultValue = "1") int page, @RequestParam(name = "page_size", defaultValue = "10") int pageSize, @RequestParam(defaultValue = "") String keyword, @RequestParam(name = "category_id", defaultValue = "0") Integer categoryId) { return bookService.list(page, pageSize, keyword, categoryId); } /** * 图书详情 * @param id 图书ID * @return 统一响应 */ @GetMapping("/detail/{id}") public Result detail(@PathVariable Integer id) { return bookService.detail(id); } /** * 新增图书(管理员) * @param req 请求体 * @return 统一响应 */ @PostMapping("/add") public Result add(@RequestBody BookRequest req) { SecurityUtils.requireAdmin(); return bookService.add(req); } /** * 修改图书(管理员) * @param id 图书ID * @param req 请求体 * @return 统一响应 */ @PutMapping("/update/{id}") public Result update(@PathVariable Integer id, @RequestBody BookRequest req) { SecurityUtils.requireAdmin(); return bookService.update(id, req); } /** * 删除图书(管理员) * @param id 图书ID * @return 统一响应 */ @DeleteMapping("/delete/{id}") public Result delete(@PathVariable Integer id) { SecurityUtils.requireAdmin(); return bookService.delete(id); } }<template> <div class="page-container"> <div class="search-bar"> <el-input v-model="keyword" placeholder="搜索分类名称" clearable style="width:240px" @clear="loadData" /> <el-button type="primary" @click="loadData"><el-icon><Search /></el-icon>搜索</el-button> <el-button type="success" @click="openDialog()"><el-icon><Plus /></el-icon>新增分类</el-button> </div> <div class="table-card"> <el-table :data="tableData" stripe border style="width:100%"> <el-table-column prop="id" label="ID" min-width="80" /> <el-table-column prop="name" label="分类名称" min-width="160" /> <el-table-column prop="sort" label="排序" min-width="100" /> <el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip /> <el-table-column label="操作" min-width="160" fixed="right"> <template #default="{ row }"> <el-button type="primary" link @click="openDialog(row)">编辑</el-button> <el-button type="danger" link @click="handleDelete(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="loadData" /> </div> </div> <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑分类' : '新增分类'" width="480px" destroy-on-close> <el-form ref="formRef" :model="form" :rules="rules" label-width="80px"> <el-form-item label="名称" prop="name"><el-input v-model="form.name" /></el-form-item> <el-form-item label="排序" prop="sort"><el-input-number v-model="form.sort" :min="0" /></el-form-item> <el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="handleSubmit">确定</el-button> </template> </el-dialog> </div> </template> <script setup> /** 图书分类管理页面 */ import { ref, reactive, onMounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { getCategoryList, addCategory, updateCategory, deleteCategory } from '@/api' const keyword = ref('') const page = ref(1) const pageSize = ref(10) const total = ref(0) const tableData = ref([]) const dialogVisible = ref(false) const isEdit = ref(false) const editId = ref(null) const formRef = ref(null) const form = reactive({ name: '', sort: 0, remark: '' }) const rules = { name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }] } async function loadData() { const res = await getCategoryList({ page: page.value, page_size: pageSize.value, keyword: keyword.value }) tableData.value = res.data.list total.value = res.data.total } function openDialog(row) { isEdit.value = !!row editId.value = row?.id || null form.name = row?.name || '' form.sort = row?.sort || 0 form.remark = row?.remark || '' dialogVisible.value = true } async function handleSubmit() { await formRef.value.validate() if (isEdit.value) { await updateCategory(editId.value, form) ElMessage.success('修改成功') } else { await addCategory(form) ElMessage.success('添加成功') } dialogVisible.value = false loadData() } async function handleDelete(row) { await ElMessageBox.confirm(`确定删除分类「${row.name}」吗?`, '提示', { type: 'warning' }) await deleteCategory(row.id) ElMessage.success('删除成功') loadData() } onMounted(loadData) </script><view class="container"> <view class="search-bar"> <input class="search-input" placeholder="搜索书名/作者/ISBN" value="{{keyword}}" bindinput="onKeywordInput" confirm-type="search" bindconfirm="onSearch" /> <button class="search-btn btn-primary" bindtap="onSearch">搜索</button> </view> <scroll-view scroll-x class="category-scroll"> <view class="category-tag {{categoryId === 0 ? 'active' : ''}}" bindtap="selectCategory" data-id="0">全部</view> <view class="category-tag {{categoryId === item.id ? 'active' : ''}}" wx:for="{{categories}}" wx:key="id" bindtap="selectCategory" data-id="{{item.id}}">{{item.name}}</view> </scroll-view> <view class="book-list"> <view class="book-card" wx:for="{{books}}" wx:key="id" bindtap="goDetail" data-id="{{item.id}}"> <image class="cover" src="{{item.cover_full}}" mode="aspectFill" /> <view class="info"> <view class="title">{{item.title}}</view> <view class="meta">作者:{{item.author || '未知'}}</view> <view class="meta">分类:{{item.category_name || '未分类'}}</view> <view class="stock">库存:{{item.stock_count}}</view> </view> </view> <view class="empty-tip" wx:if="{{!loading && books.length === 0}}">暂无图书</view> <view class="load-more" wx:if="{{hasMore}}" bindtap="loadMore">加载更多</view> </view> </view>