尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue3 + Element-Plus 图书管理系统开发实战:从工程化到组件复用

Vue3 + Element-Plus 图书管理系统开发实战:从工程化到组件复用 简介基于Vue3与Element-Plus构建的图书管理系统源码面向中高级前端开发者或需要快速搭建管理后台的工程师旨在提供一套完整可用的图书信息增删改查、借阅管理以及用户登录验证的解决方案。压缩包共33个文件含14个Vue组件、10个JavaScript脚本、2个JSON配置文件以及HTML、图片、图标等辅助资源整体大小2.41MBVue组件负责页面交互与数据展示JS脚本实现业务逻辑JSON用于系统配置目录结构清晰便于定位修改。技术选型采用vue-router与vue-pinia展示了路由管理和状态管理的现代实践有助于开发者理解组件化开发与响应式数据流也方便后端人员快速上手前端。借助Element-Plus组件库界面美观且开发高效既适合课程设计与毕设参考也适合企业后台模块复用。目前已有384人学习下载源码可灵活定制扩展具备较高的参考价值。1. 为什么图书管理系统值得用 Vue3 和 Element-Plus 重写一套很多老教材里的图书管理系统还停在 jQuery 或原生 JavaScript 阶段单页表单加几个 getElementById 就能交差但要扩展一个读者借阅记录、一个逾期统计代码就撑不住了。用 Vue3 与 Element-Plus 重做这套系统本质不是换皮肤而是把数据流、组件边界和接口层一次理清。图书管理系统的实体关系非常标准——图书、读者、借阅记录、分类正好用来演示组合式 API 怎么组织业务逻辑、表格和表单怎么高效联动。适合的人群也很明确正在做课程设计的学生、想拿 Vue3 练手的前端新人以及需要给内部项目快速搭后台管理界面的工程师。这套方案不需要后端团队配合先用 Mock 数据打通全部页面后续对接真实接口只改一层请求封装。2. 搭建 Vue3 与 Element-Plus 的开发底座从安装到源码目录规划所谓“设计源码”大多数人拿到的只是一个能跑的前端工程后续自己改功能、加页面时才发现目录乱成一团。与其等代码写坏了再重构不如在初始化阶段就把目录边界定下来。图书管理系统的规模不大但该有的分层不能少页面组件只负责渲染和交互业务逻辑放进组合式函数接口请求全部走统一封装这样才能保证读者管理、借阅管理这些后续模块往里面加的时候不动老代码。2.1 用 Vite 初始化项目并安装 Element-Plus初始化用 Vite 是当前 Vue3 项目最稳妥的选择比 Vue CLI 启动更快依赖也更干净。打开终端执行npm create vitelatest book-manager -- --template vue cd book-manager npm install npm install element-plus element-plus/icons-vue npm install vue-router4 pinia axios模板参数里的vue指定使用 Vue3 官方模板生成的是组合式 API 风格的工程。Element-Plus 的图标是独立包注册方式跟组件库本体分开后文会说明具体注册代码。vue-router 必须用 4.x 版本Pinia 是 Vue3 官方推荐的状态管理库替代 Vue2 时代的 Vuex。axios 用来发请求图书管理系统的增删改查都通过它跟后端通信本地开发阶段则用 Mock 拦截。2.2 按图书业务划分源码目录初始模板生成的 src 目录只有 main.js 和 App.vue不直接够用。参考后台管理项目常见的分层方式我一般会把业务拆成下面这样src/ api/ // 接口请求定义每个模块一个文件 assets/ // 静态资源 components/ // 通用组件如图书封面上传、分页封装 composables/ // 组合式函数如 useBookList、useBorrowRecord layout/ // 后台布局侧边栏 顶部导航 主内容区 router/ // 路由配置与守卫 stores/ // Pinia 状态如用户信息、借阅状态缓存 views/ // 页面组件图书管理、读者管理、借阅管理、统计 utils/ // 工具函数如日期格式化、ISBN 校验 mock/ // Mock 数据与拦截规则这段目录里composables 是 Vue3 相对 Vue2 变化最大的一层。以前图书增删改查的逻辑散落在各个 methods 里现在用useBookList这样的函数把这些逻辑收拢多个页面可以复用同一份代码。api 层单独抽出来是因为图书管理系统的接口路径有规律比如/api/books、/api/readers统一放一起方便改后端地址。2.3 配置路由、状态管理并挂载 Element-Plus入口文件需要同时完成 Element-Plus 的注册和路由、状态管理的挂载// src/main.js import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import zhCn from element-plus/es/locale/lang/zh-cn import App from ./App.vue import router from ./router const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus, { locale: zhCn }) app.mount(#app)locale: zhCn是 Element-Plus 的常用配置项组件的内置文案比如分页器的“共 X 条”、日期选择器的月份名称默认是英文不设置的话页面会混着中英文。Element-Plus 这种全量引入方式对图书管理系统这种中小型项目足够打包体积问题在第五章单独讲优化。路由配置按后台管理布局走侧边栏菜单对应路由表的 children// src/router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(../layout/Index.vue), redirect: /books, children: [ { path: books, name: BookList, component: () import(../views/book/BookList.vue) }, { path: readers, name: ReaderList, component: () import(../views/reader/ReaderList.vue) }, { path: borrow, name: BorrowList, component: () import(../views/borrow/BorrowList.vue) } ] } ] export default createRouter({ history: createWebHistory(), routes })routes 里的组件都用动态导入这样路由懒加载自动生效首屏只加载当前页面需要的代码。图书管理系统一般有三个核心路由就够起步后续加统计分析页面时往 children 里追加即可。3. 用 Element-Plus 组件把图书管理核心页面做厚组件库的价值不在把页面拼出来而是把表格分页、表单校验这些重复劳动降到最低。图书管理系统最核心的页面是图书列表和图书编辑弹窗这两块做扎实读者管理、借阅记录页面就是换个数据字段的事。3.1 图书列表页表格、分页与检索条件联动图书列表页是整个系统的门面涉及 Element-Plus 的 el-table、el-pagination、el-form 三组件的协作。先看模板部分template el-card shadownever el-form :modelqueryParams inline el-form-item label书名 el-input v-modelqueryParams.title placeholder模糊搜索 clearable / /el-form-item el-form-item label分类 el-select v-modelqueryParams.category placeholder全部 clearable el-option v-forc in categories :keyc :labelc :valuec / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button el-button clickresetSearch重置/el-button /el-form-item /el-form el-table :databookList v-loadingloading border el-table-column propisbn labelISBN width150 / el-table-column proptitle label书名 min-width200 show-overflow-tooltip / el-table-column propauthor label作者 width120 / el-table-column propcategory label分类 width100 / el-table-column propstock label库存 width80 aligncenter / el-table-column label操作 width180 fixedright template #default{ row } el-button link typeprimary clickopenEditor(row)编辑/el-button el-button link typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table el-pagination v-model:current-pagequeryParams.page v-model:page-sizequeryParams.pageSize :totaltotal layouttotal, sizes, prev, pager, next :page-sizes[10, 20, 50] changefetchList / /el-card /template表格列里show-overflow-tooltip是一个容易忽略的属性书名过长时自动截断鼠标悬停显示完整书名避免表格被撑乱。分页组件用v-model:current-page和v-model:page-size双向绑定查询参数这是 Element-Plus 分页器标准的写法页码或每页条数变化时触发 change 事件重新拉数据。// src/views/book/BookList.vue 脚本部分 import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import { getBookList, deleteBook } from ../../api/book const bookList ref([]) const total ref(0) const loading ref(false) const categories [文学, 历史, 计算机, 科学] const queryParams reactive({ title: , category: , page: 1, pageSize: 10 }) const fetchList async () { loading.value true try { const { rows, total: count } await getBookList(queryParams) bookList.value rows total.value count } finally { loading.value false } } const handleSearch () { queryParams.page 1 // 重置页码避免搜到空页 fetchList() } const resetSearch () { queryParams.title queryParams.category queryParams.page 1 fetchList() } onMounted(fetchList)loading用 ref 而不是 reactive因为页面只有这一个加载状态ref 语义更清晰。查询时先重置页码是常见坑位不重置的话当前在第 5 页输入书名搜索后第 5 页可能只有 0 条数据用户以为搜不到实际上数据在第 1 页。删除操作前要二次确认直接调用 Element-Plus 的 ElMessageBoxconst handleDelete async (row) { await ElMessageBox.confirm(确认删除《${row.title}》, 删除确认, { type: warning }) await deleteBook(row.id) ElMessage.success(删除成功) // 当前页只剩一条数据时自动回退一页 if (bookList.value.length 1 queryParams.page 1) { queryParams.page - 1 } fetchList() }删除后处理当前页最后一条数据是一个细节逻辑不处理的话删除后页码不变页面会短暂空白。这是真实使用中经常会遇到的体验问题。3.2 图书新增与编辑的弹窗表单和校验规则图书的新增和编辑共用一个 el-dialog 对话框通过弹窗标题和表单初始值区分两者。Element-Plus 的表单校验依赖 el-form 的 model、rules 和 el-form-item 的 prop 属性el-dialog v-modeldialogVisible :titleform.id ? 编辑图书 : 新增图书 width520px destroy-on-close el-form refformRef :modelform :rulesrules label-width80px el-form-item labelISBN propisbn el-input v-modelform.isbn placeholder13 位数字 / /el-form-item el-form-item label书名 proptitle el-input v-modelform.title / /el-form-item el-form-item label分类 propcategory el-select v-modelform.category placeholder请选择 el-option v-forc in categories :keyc :labelc :valuec / /el-select /el-form-item /el-form template #footer el-button clickdialogVisible false取消/el-button el-button typeprimary clickhandleSubmit保存/el-button /template /el-dialogdestroy-on-close属性值得单独说明弹窗关闭时销毁内容下次打开重新创建表单组件。图书编辑和新增如果共用同一个 form 对象不销毁的话上一次的编辑数据会残留在表单里出现“新增时表单已经填了上一本书的内容”这种问题。校验规则放在脚本里const rules { isbn: [ { required: true, message: 请输入 ISBN, trigger: blur }, { pattern: /^\d{13}$/, message: ISBN 必须为 13 位数字, trigger: blur } ], title: [ { required: true, message: 请输入书名, trigger: blur }, { min: 1, max: 50, message: 书名长度不能超过 50 字, trigger: blur } ], category: [{ required: true, message: 请选择分类, trigger: change }] }trigger的取值有讲究输入类组件用blur下拉选择类组件用change。写成 blur 对 select 也生效但用户选完分类后焦点不离开错误提示不会消失体验割裂。提交时先调用表单组件暴露的 validate 方法全部通过再走接口const handleSubmit async () { await formRef.value.validate() if (form.value.id) { await updateBook(form.value) } else { await createBook(form.value) } ElMessage.success(form.value.id ? 更新成功 : 新增成功) dialogVisible.value false fetchList() }validate 返回的是 Promise校验不通过时抛异常配合 await 可以直接中断后面的提交逻辑不需要手动判断返回值。3.3 借阅与归还的状态字段设计和状态流转图书借阅是比增删改查再多一层业务状态的功能。数据库或 Mock 数据里借阅记录通常用 status 字段标识状态前端根据状态渲染不同的标签和操作按钮status含义标签颜色可执行操作0借出中warning归还1已归还success查看详情2已逾期danger归还并记罚金模板里用 el-tag 根据状态渲染标签操作列按状态决定显示哪些按钮el-table-column label状态 width100 aligncenter template #default{ row } el-tag :typestatusMap[row.status].type{{ statusMap[row.status].label }}/el-tag /template /el-table-column el-table-column label操作 width120 template #default{ row } el-button v-ifrow.status 0 || row.status 2 link typeprimary clickhandleReturn(row) 归还 /el-button /template /el-table-column状态驱动按钮显隐是一种比写死在接口里更灵活的设计。后端接口通常不做“能否归还”的判断前端根据 status 控制入口后端再校验一次两层配合保障数据安全。归还操作的接口调用和提示在此不做展开重点在于理解了状态机设计之后逾期计算、罚金记录都能顺着这个模型扩展。4. 接口层设计Axios 封装、Mock 数据与组件复用图书管理系统的前端源码里接口层往往是初学者最容易忽略的地方。几乎所有页面都需要发请求如果把 axios 直接写进每个组件后端地址改一次要动十几个文件。封装请求层、用 Mock 模拟真实接口是让整套源码具备后续开发能力的关键。4.1 用 Axios 统一封装请求与错误处理在 utils 目录建 request.js创建 axios 实例并配置拦截器// src/utils/request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带 token request.interceptors.request.use((config) { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( (response) { const { code, data, message } response.data if (code 0) { return data } ElMessage.error(message || 请求失败) return Promise.reject(new Error(message)) }, (error) { ElMessage.error(error.response?.data?.message || 网络异常) return Promise.reject(error) } ) export default request这里把后端返回结构约定为{ code, data, message }code 为 0 表示成功。拦截器的好处是页面代码不用每次判断 code直接拿到 data 使用。token 从 localStorage 读取是一种常见做法图书管理系统如果需要登录鉴权这个位置是统一的注入点。timeout 设为 10 秒防止接口挂起时页面一直转圈。封装好请求实例后api 模块里的代码就非常薄了// src/api/book.js import request from ../utils/request export const getBookList (params) request.get(/books, { params }) export const createBook (data) request.post(/books, data) export const updateBook (data) request.put(/books/${data.id}, data) export const deleteBook (id) request.delete(/books/${id})接口函数只做参数透传和路径拼接业务层的调用方不感知 HTTP 细节。新增图书用 POST更新用 PUT删除用 DELETE借阅记录模块的接口同理。4.2 用 vite-plugin-mock 模拟后端数据没有后端时Mock 层决定了整套源码能不能跑起来。vite-plugin-mock 是 Vite 生态里配置最轻量的方案安装后在 vite.config.js 里启用npm install vite-plugin-mock -D// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import { viteMockServe } from vite-plugin-mock export default defineConfig({ plugins: [ vue(), viteMockServe({ mockPath: src/mock, localEnabled: true, prodEnabled: false }) ] })mockPath 指定存放 Mock 规则的文件目录localEnabled 为 true 表示本地开发环境开启拦截。在 src/mock 目录创建 book.js写图书列表的模拟接口// src/mock/book.js export default [ { url: /api/books, method: get, response: ({ query }) { const allBooks [ { id: 1, isbn: 9787111213826, title: 深入理解计算机系统, author: Randal E.Bryant, category: 计算机, stock: 5 }, { id: 2, isbn: 9787115428028, title: 算法导论, author: Thomas H. Cormen, category: 计算机, stock: 3 } ] const filtered allBooks.filter((book) { const matchTitle !query.title || book.title.includes(query.title) const matchCategory !query.category || book.category query.category return matchTitle matchCategory }) return { code: 0, data: { rows: filtered, total: filtered.length } } } } ]Mock 文件里写模拟数据时最好把过滤逻辑也模拟出来。否则前端分页查询调通了换成真实后端时才发现条件过滤根本没实现。这里用了数组的 filter 方法分别匹配 title 的模糊查询和 category 的精确匹配与前端查询参数的字段一一对应。4.3 组件复用与源码可维护性图书管理系统里有两类组件值得从页面里抽离一类是跨页面复用的如图书封面选择器另一类是逻辑复杂到影响页面阅读的如 ISBN 自动格式化输入框。抽离时用 defineProps 和 defineEmits 定义接口!-- components/IsbnInput.vue -- script setup const props defineProps({ modelValue: { type: String, default: } }) const emit defineEmits([update:modelValue]) const handleInput (value) { // 过滤非数字字符并限制 13 位 const cleaned value.replace(/\D/g, ).slice(0, 13) emit(update:modelValue, cleaned) } /script template el-input :model-valuemodelValue inputhandleInput placeholder仅允许数字 / /template这个组件用modelValue加update:modelValue实现 v-model 协议父组件可以像使用原生输入框一样使用它。抽离后图书编辑表单、读者证号输入框都能复用 ISBN 的数字过滤逻辑这是 Element-Plus 表单体系里做自定义输入组件最常见的模式。组件/工具位置职责request.jssrc/utilsaxios 实例、拦截器、统一错误处理useBookListsrc/composables列表加载、分页、搜索逻辑IsbnInputsrc/componentsISBN 输入格式化借阅状态映射src/utils/borrowStatus.js状态到标签文案/颜色的映射5. 构建优化与源码交付前必须处理的三个坑图书管理系统做完功能后不能着急交付还有几个问题会直接影响别人拿到源码能不能跑起来。第一关是构建配置MemElement-Plus 全量引入会让打包体积接近 1MB用按需引入能瘦身一半以上。推荐用 unplugin-vue-components 和 unplugin-auto-import 两个 Vite 插件自动按需加载组件。另外把 vue、vue-router、pinia 单独拆出 vendor 块让浏览器缓存公共依赖二次加载更快。import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ], build: { rollupOptions: { output: { manualChunks: { vue: [vue, vue-router, pinia] } } } } })配置之后无需在 main.js 里全量注册 Element-Plus组件和 API 会在使用时自动导入注意 main.js 里的全量注册代码要删掉否则体积优化的效果会打折扣。第二个常见坑是路由刷新后 404。用 createWebHistory 模式时把打包产物部署到 nginx 根路径以外的目录或者直接双击 index.html 打开刷新就会白屏。nignx 里加一行 try_files 即可解决location / { try_files $uri $uri/ /index.html; }第三个坑是 Element-Plus 的中文语言包只在全局注册时生效按需引入后 el-pagination 的分页文案会退回英文。需要在入口文件里单独配置 ConfigProvider或者在使用 el-config-provider 包裹根组件传入 zh-cn 语言包。这些细节排查完再对照图书列表增删改查、借书还书流程、搜索分页组合三个场景逐项验证这套 Vue3 与 Element-Plus 的图书管理系统源码就可以正常交给下一个开发者了。本文还有配套的精品资源点击获取
返回列表