资讯详情

药品管理系统实战:Node.js+Vue+ThinkPHP前后端分离开发

📅 2026/10/5 15:32:00 | 华诺云谱 👁 阅读
药品管理系统实战:Node.js+Vue+ThinkPHP前后端分离开发
1. 聊聊这个项目一个药品管理系统的真实需求医院药品管理听起来是个老掉牙的题目但真正动手做一个能跑起来、能展示、能说明白业务流的系统难度并不低。我做这个项目时不是单纯为了实现一套增删改查而是把医院药剂科的核心痛点梳理了一遍药品主数据怎么维护、入库出库怎么保证库存准确、批号和有效期怎么追踪、近效期药品怎么提醒、不同角色应该看到哪些菜单和数据。这些业务约束决定了系统不能只是套一个CRUD框架必须把库存模型和权限模型设计清楚。我选用的技术组合是 Node.js Vue ThinkPHP这也是很多朋友关心的组合。Node.js 在这套体系里的定位是前端工程化的运行环境负责跑 npm、启动开发服务器、构建打包Vue 负责页面交互和前端路由ThinkPHP 负责后端接口和数据逻辑。看到这里你可能会问为什么后端不用 Node.js反而用 PHP这个我在后面的选型分析里会详细说。这篇博文适合正在做类似管理系统、毕业设计、或者想从零搭一个前后端分离项目的朋友内容偏实战包含了很多我踩过的坑和最终采用的方案。1.1 为什么选药品管理作为题目药品管理系统比普通的商品管理系统有意思因为药品有几个特殊属性第一药品有批准文号、生产批号、有效期库存管理必须支持同一药品多批次共存第二药品出入库必须讲究先入先出不能把旧批号压箱底第三医院场景对权限特别敏感药剂科、护士站、管理员能看到的数据和操作完全不同第四系统需要随时统计近效期药品、过期药品、库存上下限预警。这些要求如果只是硬编码在控制器里代码会非常臃肿。所以我一开始就决定把业务拆分成几个独立模块药品档案、药品分类、供应商、库存批次、出入库流水、预警统计、系统用户与权限。每个模块单独设计数据表和接口再通过清晰的关联关系把业务串起来。做完整套以后你会发现这个项目不仅是“药品管理”它其实是一个小型的进销存系统跑去商品、图书、设备等领域也能快速迁移。1.2 Node.js、Vue和ThinkPHP到底各负责什么很多人第一次听到 Node.js 和 Vue 一起出现会下意识以为后端也是 Node.js其实不是。这个项目里 Node.js 就是前端开发的基础环境Vue CLI 和 Vite 都依赖 Node.js 运行npm 包管理器也依托 Node.js。换句话说没有 Node.jsVue 工程根本跑不起来。Vue 本身是渐进式前端框架我用它来做单页面应用配合 Element Plus 组件库快速搭建管理后台界面再用 Vue Router 做页面路由、Axios 做接口请求。ThinkPHP 则承担了全部后端逻辑。我选择 ThinkPHP 而不是其他框架核心原因有三点一是 PHP 在 Web 接口开发上的效率很高ThinkPHP 的模型、验证器、中间件开箱即用适合中小型管理系统二是团队里其他成员对 PHP 更熟悉后期维护成本低三是 ThinkPHP 可以快速生成 API 接口和前端 Vue 做彻底的前后端分离。实际开发下来这种组合的节奏很舒服Vue 写页面ThinkPHP 写接口两边各自调试最后联调时只需要约定好 JSON 数据格式和状态码。2. 系统架构与数据库设计先搭好骨架我习惯在写代码之前先把架构图画清楚虽然不用画得很正式但至少要明白每一层做了什么。这个项目采用经典的前后端分离架构前端 Vue 工程运行在 Node.js 环境里通过 HTTP 请求访问 ThinkPHP 提供的 API后端 ThinkPHP 处理请求以后通过 PDO 操作 MySQL 数据库返回统一格式的 JSON。前端负责渲染和数据展示后端负责业务规则和数据持久化。这样的架构带来的最大好处是前端和后端可以并行开发。我在实际开发中先把后端接口文档用表格列出来包括路径、请求方式、参数、返回结构然后让前端同事按文档做页面我专心写后端逻辑。后面联调时基本没出现大面积返工。相比传统 PHP 模板渲染方案前后端分离虽然增加了部署复杂度但代码边界清楚尤其是权限控制和接口复用方面长远来看省心很多。2.1 前后端分离架构的边界划分我见过不少项目名义上前后端分离实际上接口返回的全是 HTML 片段或者后端人员硬要在模板里写 vue 指令最后维护起来非常混乱。我这个项目坚持了严格边界前端所有页面数据都来自 API后端不关心页面长什么样只返回 JSON。前端通过 Axios 统一处理请求头、Token、错误提示后端通过中间件处理跨域和身份校验业务控制器只负责参数接收、调用模型、返回结果。具体到目录结构前端工程分为 views、components、router、store、api 五个核心目录。views 放页面components 放复用组件router 负责路由配置store 用 Pinia 管理用户信息api 目录按模块封装接口请求。后端按模块化思想分层controller 接收请求并做参数校验service 写核心业务逻辑模型负责数据库交互。这种分层的意义在于出问题时能快速定位前端界面乱找 components接口数据不对找 api 和 controller业务规则不对找 service数据不对找模型和 SQL。2.2 核心数据表设计含字段说明数据库是系统真正的骨架我把核心表结构设计在这里方便大家在开发时直接参考。药品主表存基础信息比如药品名称、通用名、规格、单位、生产厂家、批准文号、剂型、药品分类ID、参考价格。药品分类表存分类层级可以做成无限级分类满足药剂科按抗生素、心脑血管、中成药等维度筛选的需求。库存表是重中之重我不会把库存数量直接挂在药品主表上因为同一药品可能有多个生产批号不同批号的有效期还不一样。所以我设计了药品批次表包含药品ID、批号、生产日期、有效期、入库批次号、当前库存数量。每个批次的库存单独记录出库时按效期排序先出最早批次这样才能真正实现先入先出。出入库流水表记录每一次操作的类型、数量、经办人、关联批次用于审计和统计。用户表和角色权限表负责身份认证和菜单权限控制。下面是核心数据表的简化结构字段按最小可用原则列出。表名核心字段说明drugid, name, generic_name, spec, unit, manufacturer, approval_number, category_id, status药品档案drug_categoryid, parent_id, name, sort药品分类drug_batchid, drug_id, batch_no, production_date, expire_date, stock, warehouse_id药品批次库存stock_logid, drug_id, batch_id, type, quantity, before_stock, after_stock, operator_id, remark出入库流水system_userid, username, password, real_name, role_id, status系统用户system_roleid, name, remark角色2.3 权限模块的设计思路医院系统不能所有用户看到同一套菜单药剂科的人要能处理入库、出库和报损护士站可能只需要查询药品和查看库存管理员则要管理用户和日志。我采用最经典的 RBAC 模型也就是用户关联角色、角色关联菜单权限。菜单表存的是前端路由路径比如 /drug/list、/stock/in每个菜单节点对应一个权限标识。用户登录时后端根据角色返回该用户能访问的菜单前端拿到菜单列表后动态生成路由。这样做的好处非常明显修改角色权限时不需要挨个改用户配置把菜单勾选一下就能生效。前端菜单处理好以后后端接口还得再做一层越权校验不能光靠菜单隐藏。我在后端的每个控制器方法里都会做权限判断保证即使有人绕过前端直接调接口也无法执行无权操作。前后端双重校验是管理系统的基本门面。3. 开发环境搭建Node.js、Vue、ThinkPHP的一次性配置环境搭建是项目起步最容易卡壳的地方尤其是 Node.js 和 Vue 的安装。很多人跟着网上的教程一安装就遇到权限报错、npm 源缓慢、脚手架版本不兼容等问题。我在这台 Windows 电脑上重新走了一遍完整流程从 Node.js 下载到 ThinkPHP 项目跑起来把每一步的关键点整理出来。3.1 Node.js安装与环境配置内含npm源设置Node.js 建议直接去官网下载 LTS 版本不要追新。Windows 安装包是 .msi 文件双击一路下一步即可安装完成以后会自动配置环境变量。验证是否安装成功打开命令行执行node -v和npm -v如果都能输出版本号说明安装成功。这里有个常见坑很多人安装完以后会提示npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本这是因为 PowerShell 默认执行策略限制导致的解决办法是用管理员身份运行 PowerShell执行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser再按提示输入 Y 即可。npm 默认镜像在国内下载依赖很慢我第一次跑npm install等得头皮发麻后来统一配置成淘宝镜像。执行命令npm config set registry https://registry.npmmirror.com之后所有依赖下载速度会有明显提升。如果还觉得慢也可以使用nrm工具快速切换镜像源。另外建议把全局依赖缓存目录设置到非系统盘避免 C 盘空间不足。3.2 Vue工程创建与依赖安装Vue 工程我推荐使用 Vite 而不是 Vue CLI因为 Vite 启动速度非常快热更新体验远好于 Webpack。执行npm create vitelatest drug-admin -- --template vue创建一个 Vue3 工程进入目录后执行npm install安装依赖。如果你习惯用 Vue CLI也可以执行vue create drug-admin但是 Node.js 版本必须大于 12Vue CLI 5 对 Node 版本要求还是比较友好的。工程创建完以后我会额外安装几个必备依赖element-plus提供后台表格、表单、弹窗组件element-plus/icons-vue提供菜单图标axios做 HTTP 请求pinia做状态管理vue-router做路由。安装命令如下。npm install element-plus element-plus/icons-vue npm install axios pinia vue-router这里最需要注意的是版本兼容问题。如果你用 Vue3安装 vue-router 必须使用版本 4 或以上Vuex 和 Pinia 选 Pinia 更轻松。如果你用 Vue2很多组件库要换一套方案。我建议新项目直接上 Vue3生态现在已经很成熟。3.3 ThinkPHP环境准备与项目初始化ThinkPHP 目前推荐使用 ThinkPHP 8要求 PHP 版本在 8.0 以上。我用的是 PHP 8.2运行在本地 Windows 上。你需要在电脑上先装好 PHP并把 PHP 目录加入系统环境变量。检查 PHP 版本执行php -v如果有输出说明没问题。项目初始化使用 Composer先安装 Composer然后在命令行进入项目目录执行composer create-project topthink/think tp执行完成以后进入 tp 目录用php think run启动内置服务器默认端口是 8000。浏览器访问http://127.0.0.1:8000看到欢迎页就说明 ThinkPHP 已经能跑了。数据库配置在.env文件里填写数据库类型、主机、库名、用户名、密码。我在开发时用 MySQL 8.0ThinkPHP 的数据库连接配置非常清晰填好以后用php think optimize:schema更新数据表字段缓存方便模型查询。4. 核心功能实现从登录鉴权到药品库存预警这一节是项目的重点我把从登录到库存预警的完整实现过程拆开来讲。每个模块我先讲思路再给关键代码这样你能直接照着落地。整体实现下来你会发现核心其实就是围绕“数据怎么流转”来设计。4.1 登录鉴权与接口安全设计登录接口我用 ThinkPHP 控制器写一个login方法接收用户名和密码先验证账号存在、状态正常再用 password_hash 加密存储的密码做校验校验成功后生成一个 Token 返回给前端。Token 我选的是简单的 JWT在 PHP 端用firebase/php-jwt库签发Payload 里存放用户 ID、角色 ID 和过期时间。前端把 Token 存储在 localStorage每次请求时在 Axios 拦截器里带上Authorization头。后端再写一个全局中间件从请求头取出 Token解析成功后把用户信息放入当前请求上下文解析失败直接返回 401。这样除了登录和获取验证码等接口其他接口都必须带 Token避免了后台接口裸奔的问题。public function login(Request $request) { $data $request-post(); $user User::where(username, $data[username])-find(); if (!$user || !password_verify($data[password], $user-password)) { return json([code 0, msg 用户名或密码错误]); } $payload [ uid $user-id, role_id $user-role_id, exp time() 7200 ]; $token JWT::encode($payload, env(JWT_KEY)); return json([code 1, token $token, user $user-real_name]); }4.2 动态路由与菜单权限实现前端动态路由是管理系统的亮点也是难点。很多项目选择在路由表里写死菜单然后通过 v-if 控制显隐这样的问题是如果用户知道路径可以直接在地址栏输入访问根本拦不住。我用的方案是登录后请求后端接口获取当前用户的菜单列表然后在前端通过router.addRoute动态添加路由未授权的路径就不会出现在路由表里。实现思路大致是后端菜单表存有component字段对应前端 views 目录下的文件路径比如views/drug/list.vue。前端拿到菜单数据后遍历生成路由配置再注册。这个方案需要前后端约好组件路径命名规范。同时在全局路由守卫中判断用户是否已登录没有 Token 就重定向到登录页有 Token 但菜单还没加载完就拉取菜单加载完后继续跳转。router.beforeEach(async (to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token !store.menuLoaded) { const menus await getMenuApi() const routes generateRoutes(menus) routes.forEach(r router.addRoute(r)) store.setMenuLoaded(true) next({ ...to, replace: true }) } else { next() } })动态路由配合权限标识是我强烈推荐的做法它不仅仅是用户体验问题更是后端接口安全的第一道门面。4.3 药品列表、入库出库与库存流水药品列表功能是后台管理最常见的表格页但药品列表有一个特殊需求同一个药品有多个批次列表不能单纯展示药品主表字段还需要展示当前总库存和近效期批次。所以我的查询方案是药品主表 leftJoin 药品批次表用SUM(stock)聚合出总库存用MIN(expire_date)找出最近有效期。这样在列表页就能一眼看到库存和效期风险。入库和出库是库存模块的核心操作。入库时先判断药品档案是否存在然后按供应商信息、采购批次号生成批次记录写入库存再写一条typein的流水。出库时必须按先入先出原则扣减库存我写了一个按有效期排序的批次查询先扣最早批次的库存如果第一个批次数量不够再扣第二个批次。$batches DrugBatch::where(drug_id, $drugId) -where(stock, , 0) -order(expire_date, asc) -select(); foreach ($batches as $batch) { if ($quantity 0) break; if ($batch-stock $quantity) { $batch-stock - $quantity; $batch-save(); $quantity 0; } else { $quantity - $batch-stock; $batch-stock 0; $batch-save(); } }整个入库出库过程必须用数据库事务包裹保证库存扣减和流水写入要么同时成功要么同时失败。我在调试阶段就遇到过扣了库存但流水没有记录的情况后来加上事务才彻底解决。4.4 效期预警和PDF说明书预览的小细节效期预警不能只靠前端写个定时器后端必须有一个能主动查询近效期药品的接口。我的实现是查询所有库存批次计算每个批次距离有效期还有多少天再把这个天数分成三个等级90天内是黄色预警30天内是红色预警已过期则为失效。前端用表格把预警药品列出来红色标红黄色标黄。此外药品说明书通常是一份 PDF 文件我在药品详情页直接嵌入了 PDF 预览功能。Vue 生态里可以通过vue-pdf或者直接在 iframe 里加载后端返回的文件地址来展示。如果只是简单展示 PDF用 iframe 最方便不需要额外安装大包如果要做翻页、缩放建议用 vue-pdf。上传说明书时注意用 ThinkPHP 的文件上传功能限制文件后缀为 pdf 和图片格式避免上传恶意文件。5. 部署联调与踩坑实录每次做项目最花时间的往往不是业务代码而是环境问题。这一节把我在实际开发中遇到的典型问题整理成清单尤其是搜索引擎里经常有人问的 npm 执行策略问题、依赖安装慢问题、跨域问题和伪静态配置问题。5.1 前端npm和运行时的常见坑除了前面说过的 PowerShell 执行策略问题还有一个高频坑是npm install时报node-sass安装失败。这个问题的根本原因是 node-sass 在 Node.js 新版本上需要重新编译原生模块经常卡在下载二进制文件那一步。我的解决办法是尽量使用 Vite 工程Vite 默认用的 CSS 预处理器依赖是sass也就是 Dart Sass不再依赖 node-sass兼容性好很多。如果项目必须用 node-sass可以设置镜像set SASS_BINARY_SITEhttps://npmmirror.com/mirrors/node-sass另一个常见问题是npm run dev启动时端口冲突Vite 默认监听 5173如果端口被占用会提示换新端口。我习惯在vite.config.js里显式配置:export default { server: { host: 0.0.0.0, port: 8080 } }这样就可以直接用 8080 端口访问后台管理页面。5.2 后端跨域与伪静态配置前后端分离以后前端跑在 8080后端跑在 8000端口不同必然触发跨域。跨域不是一个复杂的难题但很容易被忽略。我在 ThinkPHP 中间件里写了一个简单的跨域处理对所有请求先返回允许源、允许方法和允许头再继续执行业务逻辑。header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS); header(Access-Control-Allow-Headers: Content-Type, Authorization); if (strtoupper($_SERVER[REQUEST_METHOD]) OPTIONS) { exit(); }生产环境部署时如果使用 Nginx需要把前端所有路由都转发到 index.html否则刷新页面会 404。ThinkPHP 后端也需要配置伪静态把请求统一路由到入口文件。这一步在本地开发时可能无关紧要但部署到服务器上就会立刻遇到建议提前处理。5.3 常见问题速查表问题原因解决方案npm 无法加载文件禁止运行脚本PowerShell 执行策略限制以管理员身份执行Set-ExecutionPolicy RemoteSignednpm install 下载慢默认源在国外配置淘宝镜像npm config set registry https://registry.npmmirror.comVue 页面跨域请求失败前后端端口不一致后端加跨域头或前端配置 vite proxyThinkPHP 访问接口返回 404伪静态未配置或路由未生效配置 Nginx 伪静态规则药品出库负库存未做库存校验出库前检查批次库存不够时直接返回错误提示6. 项目收尾心得与扩展方向整个项目从零到跑通我最大的体会是不要把系统当 CRUD 来做。药品管理的核心价值在于库存准确性、效期安全、权限合规这些业务约束才是项目的灵魂。你在写代码之前一定要把数据关系理清楚尤其是批号和效期这两个隐藏字段如果漏了后面补会非常痛苦。另外一个建议是前后端联调时要提前把错误码和返回结构约定好。我这里使用的统一 JSON 格式是{code, msg, data}code 为 1 表示成功0 表示失败401 表示登录过期。前端 Axios 统一拦截 401自动跳回登录页省掉了每个页面单独判断的冗余代码。最后分享一下扩展方向。这个项目目前可以做进销存的主线流程后续可以继续加药品采购审批流、药房处方发药功能、库存盘点模块、过期药品自动报损、以及基于 ECharts 的药品消耗趋势分析。如果想把系统放到真实医院环境还需要对接基础字典和外部系统接口但核心骨架已经足够稳固。希望这篇实战分享能帮少走点弯路有问题可以在评论区留言交流。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑