ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Node.js+Vue全栈实战:从零开发个人物品管理系统

Node.js+Vue全栈实战:从零开发个人物品管理系统 搬家之后我才真正意识到东西多到一定程度找东西就成了体力活。工具箱里缺个螺丝刀、书架角落里有本多年前的旧书、衣柜深处某件外套是哪个季节买的全靠记忆硬撑。后来我干脆花了两周时间用 Node.js做后端、Vue框架做前端写了一套个人物品管理系统把所有物品按分类、位置、数量、购置信息登记进去想找的时候按关键字一搜位置直接告诉我在哪个收纳箱第几层。这篇文章把整套项目的思路完整展开需求怎么拆、技术栈为什么这么选、数据库表怎么设计、前后端接口怎么对接、部署上线会遇到哪些坑。不管你是想练全栈的初学者还是准备拿类似选题做课程设计这套从零到一的过程都能直接参考。1. 需求拆解与功能规划个人物品管理到底在管什么1.1 从生活场景倒推功能清单做这种项目最容易犯的错是一上来就写代码。先想清楚系统要解决什么问题才不会写着写着发现功能做歪了。拿我的场景来说痛点很明确物品数量大、种类杂、存放位置分散遗忘率高。围绕这个痛点我梳理出下面几个核心需求物品登记记录物品的名称、分类、存放位置、数量、购入价格、购买日期、备注和照片分类管理物品要有归属类别便于按类归档和统计多维筛选能按分类、位置、关键字搜索快速定位物品统计汇总知道一共有多少件物品、总价值多少、各类别占比借还追踪朋友借走的东西有个记录防止不知道借给谁了功能排优先级的时候我把前三个作为第一版必须完成的核心功能统计和借还放在第二版。原因很简单第一版要跑通完整的链路功能越少联调越容易出问题也越好定位。等基础稳定了再叠加功能心态完全不同。1.2 把功能拆成前后端两条线同样的需求落到技术实现上就要拆成两块。后端负责数据存储和业务逻辑提供一套接口供前端调用。比如物品列表接口要支持关键字模糊搜索、分类过滤、位置过滤还要返回分类名称而不是只有分类ID。这个返回分类名称的细节很关键前端拿到数据直接展示就行不用自己再做一次关联查询能少写不少代码。前端负责用户交互和展示。页面划分为物品列表页、新增编辑弹窗、分类管理页、统计看板。每个页面通过接口拿数据、渲染界面、收集用户输入。前后端各管各的前端不直接操作数据库后端不写页面逻辑职责清晰也好测试。这套拆分思路虽然是老生常谈但在个人项目里特别容易被忽略。一个人开发时容易图省事把SQL直接写在前端或者把页面逻辑硬塞到后端短期看是快后期加功能就是灾难。后面所有章节都建立在前后端分离这个基础上先把这个意识树立起来后面的路才顺。2. 技术选型为什么偏偏是Node.js和Vue2.1 后端选用Node.js的理由先抛开哪个技术更好这种无意义的问题聊实际需求。个人物品管理系统属于典型的数据管理类应用并发量不高业务逻辑不复杂核心诉求是快速开发、容易维护。Java SpringBoot在这个场景下不是不行但环境配置和工程规范相对重一个简单的增删改查要铺一大堆配置。Python Flask/Django也行不过如果你想跟前端共用一种语言Node.js显然更顺。Node.js的优势体现在三点。第一语言统一。前后端都用JavaScript数组、对象、函数式处理这些知识在两端复用不用在脑子里切换语言模式。我写后端接口时处理查询参数的方式和前端处理数据的方式一脉相承心智负担小很多。第二异步IO非常贴合这类读多写少的应用。Node.js处理并发请求时不需要像传统同步模型那样为每个请求分配独立线程事件循环机制让它在低并发场景下表现非常够用。第三npm生态极其庞大。Express、Cors、better-sqlite3、multer这些库装完就能用几分钟就能把服务端骨架搭起来。相比之下用其他语言要处理依赖管理工作量大不少。2.2 前端选用Vue框架的考量Vue在国内开发者群体里的普及度高学习曲线相对平缓模板语法直观组件化开发天然适合管理后台类应用。我的页面构成很简单顶部导航栏、左侧分类列表、中间物品表格、右侧统计卡片每个区域都可以独立成组件组件之间通过props和事件通信逻辑划分非常清晰。Vue的响应式系统对这类表单密集型应用很友好。用户在新增物品弹窗里填写表单数据用v-model绑定校验逻辑写在表单提交前一切改动页面即时反馈写起来比手动操作DOM要舒服得多。版本选择上我直接用Vue 3的Composition API配合Vite。Vue 2已经到了维护模式新项目没必要再用。Vite的冷启动速度和热更新体验比webpack时代的开发服务器好太多用过一次就回不去了。状态管理用了Pinia替代VuexAPI设计更简洁对TypeScript支持也更好虽然没有强需求但顺手就装了。组件库选的是Element Plus表格、表单、弹窗、消息提示这些现成组件正好覆盖需求比自己从零写样式效率高得多。2.3 数据库和中间件怎么选数据存储我最终用了SQLite。个人项目的数据量撑死几千条SQLite一个文件搞定全部数据零配置、免安装、直接嵌入应用备份就是复制文件实在太适合这个场景。而且better-sqlite3这个库是同步API写起来比异步的数据库客户端更直观返回的就是真实结果对调试很友好。如果你打算把这个系统做成多人使用的线上版本那可以换成MySQL用mysql2库连接配合连接池管理。基本SQL语句是一样的需要改的地方主要是连接初始化和并发处理。这两者的选择对后续章节的代码实现影响不大我在文中会默认以SQLite为主需要MySQL时单独标注。开发调试阶段还离不开nodemon监听文件变化自动重启服务省去手动重启的麻烦。前端用Vite自带的热更新等于改完代码保存就能看到效果整个开发周期非常有节奏感。3. 环境搭建与工程初始化最容易翻车的环节3.1 Node.js安装与环境变量配置工欲善其事必先利其器。先去Node.js官网下载LTS版本Windows用户直接下载msi安装包一路下一步。这里有个细节安装时确保勾选Add to PATH选项否则后面命令行里找不到node和npm。安装完成后终端验证node -v npm -v能正常输出版本号就说明没问题。如果提示找不到命令去系统属性里检查环境变量看Node.js安装目录是否在Path中。Windows用户还经常碰到一个经典问题在PowerShell里执行npm命令时报错npm.ps1无法加载因为在此系统上禁止运行脚本。这不是npm装坏了而是PowerShell的执行策略默认限制脚本运行。解决办法是打开PowerShell执行Set-ExecutionPolicy RemoteSigned选择是确认再执行npm命令就不会报错了。然后记得修改npm镜像源。默认源在某些网络环境下下载依赖慢到让人怀疑人生我改成了npmmirror的这个源头安装速度提升非常明显npm config set registry https://registry.npmmirror.com3.2 用Vite快速初始化前端工程前端工程我直接用Vite脚手架生成命名item-manage-webnpm create vitelatest item-manage-web -- --template vue cd item-manage-web npm install这里有个容易掉坑的地方如果你在Windows PowerShell里执行这条命令模板参数可能需要写成带引号的格式否则会被解析成两个参数。更稳妥的做法是直接运行npm create vitelatest然后根据交互提示选择项目名称、选择框架、选择是否使用TypeScript全程手动选反而更可控。项目生成后安装路由、状态管理、组件库和HTTP工具npm install vue-router4 pinia axios element-plusElement Plus按需引入还是全量引入个人项目图省事就全量引入main.js里一行app.use(ElementPlus)搞定打包体积大一点但开发效率高。3.3 后端工程骨架与依赖管理后端单独建一个目录命名item-manage-servermkdir item-manage-server cd item-manage-server npm init -y安装核心依赖npm install express cors better-sqlite3 multer npm install -D nodemon随后在package.json里配置启动脚本scripts: { dev: nodemon app.js, start: node app.js }为什么用app.js作为入口因为主模块可以拆分成三个部分应用实例初始化、路由挂载、数据库初始化。拆开写的好处是将来写单元测试时可以直接导入app实例不需要真的启动监听端口。工程目录结构我建议这样组织干净且容易扩展item-manage-server/ ├── app.js # 应用入口 ├── db.js # 数据库连接与建表 ├── routes/ │ ├── categories.js # 分类路由 │ └── items.js # 物品路由 └── uploads/ # 图片上传目录导航条和列表页分开写之后你会发现每个文件的职责都很单一出问题直接定位到文件不用在大块代码里翻找。4. 核心功能模块设计与实现前端页面与接口怎么写4.1 数据库表结构设计表结构这一步决定了整个系统能做什么、不能做什么。设计不好后期改表简直生不如死。我设计了两个核心表CREATE TABLE categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category_id INTEGER NOT NULL, location TEXT, quantity INTEGER DEFAULT 1, price REAL DEFAULT 0, buy_date TEXT, note TEXT, photo TEXT, created_at TEXT DEFAULT (datetime(now)), FOREIGN KEY (category_id) REFERENCES categories(id) );这里有两个设计细节值得展开。第一个是category_id用外键关联而不是直接把分类名称存进items表。表面上看存字符串数码设备要比存数字ID更直观。但一旦分类改名所有关联的物品记录都要跟着改数据冗余就会变成数据维护负担。用了外键关联改分类名称只需要改categories表一条记录所有物品自动关联新名称这就是关系型数据库设计的基本原则。第二个是quantity和price分开存。很多物品同类会有多件比如数据线、螺丝刀都是一盒一盒买数量字段必须存在价格字段单位用元类型用REAL而不是INTEGER因为有些物品的价格带小数。创建索引的时候我给items表的name和category_id各建了一个普通索引。数据量小时感受不到区别但搜索是高频操作索引的收益会随着数据积累越来越明显。4.2 后端接口设计RESTful风格的API后端接口采用RESTful风格围绕分类和物品两个资源设计JSON接口方法路径功能说明GET/api/categories获取全部分类按创建时间正序POST/api/categories新增分类名称不能重复DELETE/api/categories/:id删除分类分类下有物品时拒绝GET/api/items筛选物品列表支持关键字、分类、位置POST/api/items新增物品表单校验PUT/api/items/:id修改物品传完整对象DELETE/api/items/:id删除物品同时删除照片文件物品列表这个接口稍微复杂一点因为要支持组合查询。假设前端传入keyword表示关键字、categoryId表示分类ID、location表示位置后端的实现思路是用SQL拼接router.get(/, (req, res) { const { keyword, categoryId, location } req.query; let sql SELECT i.*, c.name AS category_name FROM items i JOIN categories c ON i.category_id c.id WHERE 11 ; const params []; if (keyword) { sql AND (i.name LIKE ? OR i.note LIKE ?); params.push(%${keyword}%, %${keyword}%); } if (categoryId) { sql AND i.category_id ?; params.push(categoryId); } if (location) { sql AND i.location LIKE ?; params.push(%${location}%); } sql ORDER BY i.created_at DESC; const rows db.prepare(sql).all(...params); res.json({ code: 0, data: rows }); });这里WHERE 11看起来像老程序员喜欢的写法实际用处是让后续追加条件时不用判断当前条件是否是第一个。配合LIKE模糊查询前端只要把输入框的内容传过来后端就能同时匹配物品名称和备注。如果有空条件直接跳过即可。新增物品的接口要处理图片上传。我用multer中间件实现上传的图片存放在server/uploads目录下文件名使用时间戳加随机数避免重名文件信息存到items表的photo字段。这个时候photo字段存的是一个相对路径例如/uploads/1688292345678.jpg前端用http://localhost:3000加这个路径就能访问到图片。后端要额外挂载一个静态资源目录app.use(/uploads, express.static(uploads));4.3 前端路由与页面结构前端路由使用vue-router采用常规的静态路由配置没有用动态路由。动态路由适合权限系统或菜单由后端加载的场景个人系统就三个页面用动态路由反而增加复杂度。路由配置如下const routes [ { path: /, component: DashboardPage, meta: { title: 统计看板 } }, { path: /items, component: ItemListPage, meta: { title: 物品管理 } }, { path: /categories, component: CategoryPage, meta: { title: 分类管理 } } ];物品列表页是这个系统的核心页面。我用Element Plus的el-table展示数据列配置包括物品名称、分类、位置、数量、价格、购买日期和操作按钮。操作按钮有编辑和删除两个编辑点击后弹窗回填当前行的数据删除前弹出确认框防止误操作。最值得说的功能区是筛选条件栏。页面上方放三个控件关键字输入框、分类下拉框、位置输入框。分类下拉框的数据从/api/categories接口获取位置输入框我做成标签输入的形式方便录入多个位置关键词。筛选逻辑最简单的实现方式是监听筛选值变化每次变化重新请求接口。这比点搜索按钮再请求体验更顺畅前端代码量也少watch([keyword, categoryId, location], () { loadItems(); });这里要注意防抖处理否则每次切换分类都会立即发请求用户连续操作会产生大量无效请求。用setTimeout加cleanup就能实现代码不超过十行。新增和编辑共用同一个弹窗组件。弹窗内容是一个el-form表单字段为名称、分类、位置、数量、价格、购买日期、备注、图片。表单校验规则写在表单配置里必填项是名称和分类数量默认值为1并限制为非负整数。提交时判断是新增还是编辑分别调用POST或PUT接口成功后刷新列表并清空表单。分类管理页面相对简单就是一个输入框加一个列表。输入框让用户输入新分类名称点击添加就POST到后端。列表展示当前所有分类右侧提供删除按钮。有一点要注意删除分类前要检查分类下是否存在物品否则用户会看到分类消失但物品还在的诡异现象。后端应当在分类有物品时返回错误码前端把这个错误码转成提示文案。统计看板则是把物品总数、分类总数、总价值、各分类物品数量占比用卡片展示。这些数据可以单独提供一个统计接口比如GET /api/items/stats/summary后端用几条SQL聚合查询返回前端拿到数据直接渲染。没必要把统计逻辑放在前端因为前端看不到原始数据表。5. 联调、部署与常见问题排查从能跑到稳定跑5.1 前后端联调代理配置省去跨域麻烦前后端都开发完最重要的一步就是把它们连起来。如果前端直接请求后端的地址比如axios请求http://localhost:3000/api/items浏览器的同源策略会拦截响应报跨域错误。解决办法有两个。第一个是在后端开启CORS中间件const cors require(cors); app.use(cors());这个方案一行代码搞定缺点是生产环境如果要限制跨域来源会比较麻烦。第二个方案是前端开发服务器配置代理。在Vite的vite.config.js文件里export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } });这样前端请求/api/items时开发服务器会把请求转发给后端的3000端口浏览器看到请求来自同一个端口没有跨域问题。生产环境中前端打包后的静态文件直接由后端服务托管也天然不存在跨域问题。我实际项目里两个方案都用了本地开发靠代理生产靠托管双保险。联调中还要注意接口返回的数据格式。我在后端统一约定成{ code: 0, data: ... }的结构code为0表示成功非0表示业务异常data放实际数据。前端封装的axios请求拦截器里统一处理非0的code统一弹出错误提示这样后端代码和前端代码各写一套就能对上最怕两边各玩各的数据格式。5.2 开发中遇到的典型问题与排查思路从零开发到稳定运行我踩过的坑值得列出来给后来人当参考。第一个就是前面提到的PowerShell执行策略问题。安装完Node.js发现npm命令跑不了第一反应容易怀疑安装包有问题其实只是执行策略限制。设置成RemoteSigned就好这个设置允许本机脚本运行远程下载的脚本需要签名安全性和便捷性均衡。第二个是端口占用。启动后端时提示端口3000被占用很多新手直接换端口但换着换着前后端配置就乱了。我的习惯是查一下到底谁占用了端口netstat -ano | findstr :3000拿到进程ID后在任务管理器里找到对应进程结束掉或者用命令直接清掉。搞清楚占用来源再清理比盲目换端口要靠谱。第三个是中文乱码问题。数据写入SQLite时正常前端页面上显示却是乱码。这个问题的根源多半出在HTTP响应头的字符集设置。Express默认返回text/html格式没有明确指定charsetutf-8时某些浏览器的默认解析方式会导致中文乱码。解决方案是在Express入口处设置统一响应头app.use((req, res, next) { res.setHeader(Content-Type, application/json; charsetutf-8); next(); });或者干脆在使用express.json()中间件后显式配置字符集。第四个是图片上传后访问不到。排错顺序是先检查uploads目录是否创建成功再检查静态资源中间件是否挂载最后检查前端引用的路径格式是否正确。规律是统一存相对路径、以/uploads开头、不存绝对路径因为绝对路径在不同机器的表现完全不同到了部署阶段绝对会踩雷。第五个是接口返回数据为空但前端页面报错。常见原因是后端查到的数据里某些字段为null前端渲染时直接读null的属性报错。处理方式是在前端的表格列定义里做兜底展示比如用--代替空值这些细节看着小但对使用体验影响很大。5.3 打包部署从开发环境到正式运行本地开发完成就该部署了。个人项目部署方案不需要很复杂最简单可靠的方式是前端打包成静态文件后端用Node.js托管这些静态文件并继续提供API服务。先在前端项目里执行npm run build生成dist目录。把这个目录里的文件全部复制到后端的public目录下然后在app.js里挂载app.use(express.static(public)); app.get(*, (req, res) { res.sendFile(path.join(__dirname, public, index.html)); });这里的通配符路由非常关键。因为前端用的是history模式的Vue Router浏览器直接访问/items这样的深链接时需要后端把请求重定向到index.html否则刷新页面会得到404。这也是很多前端项目部署后刷新报错的根源我在这一步卡了将近一个小时才反应过来。服务进程管理我推荐用pm2。安装npm install -g pm2 pm2 start app.js --name item-manage pm2 savepm2的用处是让Node.js进程常驻后台服务器重启后自动拉起服务并且输出日志到指定文件排查线上问题比裸跑node进程方便得多。数据备份更简单SQLite数据库就一个.db文件定期拷贝并存储到另一个磁盘就算备份完成。我甚至写了一个简单的定时任务脚本每天凌晨拷贝一次数据库文件加上了日期后缀。个人项目不需要复杂的备份方案保证文件不丢就足够了。5.4 后续扩展方向这套系统做完之后我给它加了不少实用的扩展功能。第一个是二维码标签。每件物品在新增时自动生成一个二维码包含物品ID和名称打印出来贴到收纳箱上。手机扫一扫就能直接跳到系统里对应的物品详情页找东西找编号的目标一下子明确了。第二个是Excel导入导出。批量导入物品数据比一条一条新增效率高得多导出功能则方便月底盘点。pinia状态的实现很简单后端用exceljs库生成Excel文件前端用Blob接收并下载。这个功能对个人用户是刚性需求。第三个是定期盘点提醒。给物品加上最后盘点日期字段超过三个月没盘点的物品在统计看板里高亮提示提醒自己定期整理和清理闲置物品这已经超越技术层面变成一个好习惯的养成工具了。整体体验下来用Node.js和Vue框架做这个系统最顺手的地方在于所有环节都能用JavaScript解决从写接口到写页面再到调试工具流程顺滑得不像是从零搭起来的一套应用。每个人闲置物品的情况不一样管理需求也千差万别如果你想自己动手改造一版建议先把你自己的物品清单和分类方式梳理出来再对照本文的思路去调整功能模块会比直接照搬代码更贴合实际使用场景。
返回列表