☰
SpringBoot+Vue药店管理系统实战:从数据库设计到进销存落地
2026/10/20 4:11:58 网站建设 项目流程

直接进入正题。做JavaWeb课程设计或者毕业设计,十个里面至少有七八个会选“管理系统”这类题目,而药店管理系统又是其中非常经典的选题。市面上能搜到的成品、半成品源码一大堆,但真能跑通、能讲清楚、能应付答辩的往往没几个。这篇就把我用SpringBoot+Vue从零搭一套web药店管理系统的完整过程拆开来讲,包括技术选型的理由、数据库怎么设计、权限怎么做、进销存核心逻辑怎么落地,以及最容易被坑的细节。准备动手做类似项目的同学,或者已经选了这类题目正在挠头的,这篇可以直接当操作手册用。

1. 项目整体设计与思路拆解

1.1 技术选型:为什么是SpringBoot+Vue,而不是别的

做管理系统,技术栈其实就那么几条路。有人用JSP+Servlet,有人用Python的Django/Flask,还有人选C#的.NET。我之所以定SpringBoot+Vue这套组合,核心考量是:前后端分离,符合当前主流开发模式,而且SpringBoot对Java生态的整合太友好了,MyBatis做持久层灵活度高,适合这种表结构相对清晰、查询条件多的业务系统。

具体来说,SpringBoot负责提供RESTful API,Vue负责页面渲染和交互,MySQL存数据,MyBatis处理SQL映射。这套组合的好处有三个:

  • 前后端彻底解耦,前端调接口拿JSON数据,后端不关心页面长什么样,改前端不用动后端,改后端接口只要约定不变,前端也不受影响。
  • SpringBoot内置Tomcat,打成一个jar包就能跑,不用单独装服务器配环境,部署省事。
  • MyBatis写SQL很直观,尤其适合这种需要自定义多表联查、动态条件的场景,比Hibernate那种全自动ORM更可控。

如果只是应付一个课设,JSP+Servlet确实更快,但说实话,那套东西拿到面试或者后续项目里基本用不上。既然要做,就做一套能写进简历、能拿出来讲技术亮点的方案。

1.2 功能模块划分:药店管理系统到底管什么

药店管理系统,核心是“进销存”三个字,再加“钱”和“人”。我最终拆出来的模块是这样的:

  • 登录与权限管理:系统有管理员、药师、收银员三种角色,不同角色看到的菜单和能操作的功能不一样。
  • 药品信息管理:药品的基础信息维护,包括名称、通用名、规格、生产厂家、批准文号、有效期、库存上下限等。
  • 药品入库管理:采购入库、退货入库,入库后库存自动增加。
  • 药品出库管理:销售出库、报损出库,出库后库存自动扣减。
  • 库存查询与预警:实时库存查询、低库存和高库存预警、近效期药品提醒。
  • 销售管理:前台收银、销售记录、销售退货。
  • 供应商管理:维护供应商信息,入库单关联供应商。
  • 统计报表:按药品、按时间维度的销售统计,进销存流水汇总。

这个模块划分是照着真实药店的业务场景来的,不是凭空想出来的。很多课设项目只做了个简单的CRUD,药品增删改查加个登录就完事,那种项目答辩时候老师随便一问“库存怎么扣的?进货和卖货并发怎么办?”就露馅了。既然是做管理系统,进销存这条链路必须完整跑通,才算真正“会做”。

1.3 业务流程梳理:从进货到卖货的一整条线

系统的核心业务流程是这样的:管理员联系供应商采购药品,在系统里录一张入库单,入库单包含多个药品明细,提交后库存批量增加。前台收银员卖药时,选择药品、填写数量、计算总价、收款,提交后生成一条销售记录,同时库存扣减。如果客户退货,走销售退货流程,库存加回来。报损药品走报损出库,注明原因。

这个过程看起来简单,但很多细节需要注意。比如一张入库单提交时,需要先校验药品是否存在,如果不存在要么自动创建药品信息,要么提示先维护药品基础资料;入库数量要是负数,必须拦下来。销售出库时,库存够不够,不够的药品不能卖;近效期的药品销售时要有提醒,避免把快过期的药卖给顾客。这些都是业务层面必须考虑的约束,写代码之前想清楚,写后端的时候才不会漏。

关于角色权限,我用了最简单也最稳妥的方案:后端基于拦截器校验登录状态,细分的功能权限在菜单层面控制。真要上Spring Security加JWT,课设项目里往往会把自己绕晕,而且面试官问起来,说不清没必要。权限这块我后面单独讲,先按下不表。

2. 数据库设计与核心表结构

2.1 建表思路:核心表其实只有九张

做管理系统,数据库设计是地基。地桩打歪了,后面装修再漂亮也白搭。我这套的数据库一共九张核心表:

  • sys_user:用户表,存登录账号、密码、角色
  • supplier:供应商表
  • medicine:药品表
  • medicine_type:药品分类表
  • stock:库存表(也可以把库存字段直接放在药品表里,但拆出来更清晰)
  • purchase_order:入库单主表
  • purchase_order_item:入库单明细表
  • sale_order:销售单主表
  • sale_order_item:销售单明细表

另外如果要做药品近效期预警,medicine表里就要有有效期字段。如果要做统计报表,sale_order_item里的销售时间和数量字段必须有索引,不然数据量大了之后统计查询会慢。

表和表之间的关系大概是这样的:供应商是入库单的一方,入库单和入库明细是一对多;销售单和销售明细是一对多;药品和库存明细是一对一。数据库外键我建了,但在代码层面没有强依赖外键做联查,还是用MyBatis手写SQL连表查。外键在数据一致性上起个兜底作用,日常开发不依赖它,因为外键约束在高并发场景下会有性能损耗,而且MyBatis这种半自动ORM本来就不怎么管外键。

2.2 药品表设计的几个关键字段

药品表是系统的核心表,字段设计直接影响后续所有功能的实现。我是这样设计的:

  • id:主键,自增
  • medicine_code:药品编码,唯一,方便扫码或用编码检索
  • name:药品名称
  • generic_name:通用名
  • type_id:分类ID,关联分类表
  • specification:规格,比如"0.25g*24粒"
  • manufacturer:生产厂家
  • approval_number:批准文号,比如"国药准字H12345678"
  • unit:单位,盒/瓶/袋
  • purchase_price:采购价
  • sale_price:零售价
  • stock_quantity:当前库存量
  • stock_min,stock_max:库存上下限,用于预警
  • expiration_date:有效期至
  • create_time,update_time:创建和更新时间

这里要说一下为什么库存字段放药品表而不是单独一张表。两种方案都能实现,但放在药品表里查询简单、修改直接,每次进出库都update一次stock_quantity就行。单独建库存表的好处是能记录库存流水,但做课设规模的项目,药品表直接带库存字段就够用了。如果后续想升级,再单独加一张stock_log流水表即可。

2.3 入库单和销售单:用主从表结构

入库单和销售单都用主从表设计。主表存单号、操作人、时间、总金额、供应商/客户信息、备注这类整体信息,从表存每一条药品明细。这样做的好处是能真实还原一次业务操作的具体内容,而不是把多条操作拆散在流水表里。

入库单主表purchase_order字段:order_no(单号)、supplier_id、operator_id(操作人)、total_amount(总金额)、remark、create_time。

入库单明细表purchase_order_item字段:order_id、medicine_id、quantity(数量)、purchase_price(采购价)、amount(单条金额)。

销售单主表sale_order字段:order_no、cashier_id(收银员)、total_amount、pay_type(支付方式)、create_time。

销售单明细表sale_order_item字段:order_id、medicine_id、quantity、sale_price、amount。

单号的生成规则我也说下,有学生直接用数据库自增id当单号,这样其实不好。一是别人能从单号推断出你们家系统一天卖了多少单,二是合并数据或者做数据迁移的时候容易冲突。比较常用的做法是:日期+随机数,比如202501181530001234,或者日期+自增序列。我用的是"yyyyMMddHHmmss"+四位随机数的方案,基本够用。

2.4 数据库初始化脚本:手写SQL还是用工具生成

好的做法是直接手写SQL建库建表,加一些测试数据,然后把它保存成init.sql文件。好处是别人拿到源码可以直接执行,自带数据展示效果。很多人用Navicat或者MySQL Workbench可视化的方式建表,建完就完事了,完全不沉淀脚本文件。如果项目要交付给别人跑起来,一定要把创建数据库、建表、插入测试数据的SQL整理成一个文件。这个细节在答辩的时候也是加分项,老师会认为你懂工程化规范。

测试数据这一块,建议每种分类放几条,药品放十几条,供应商放三四家,再手搓一两条入库单和销售单,这样前端页面跑起来就有内容可看,不至于白屏空列表。这一条极其实用。

3. 后端核心实现:SpringBoot+MyBatis的关键代码

3.1 项目结构:包名和分层怎么安排

我用Maven构建,SpringBoot 2.7.x版本,Java 8(稳定兼容),MyBatis用mybatis-spring-boot-starter 2.2.2。包结构如下:

com.xxx.pharmacy ├── controller # 控制层,接收请求 ├── service # 业务层,处理业务逻辑 ├── mapper # MyBatis的Mapper接口 ├── entity # 实体类 ├── dto # 请求/响应对象 ├── common # 通用类:Result、异常处理等 ├── config # 配置类:拦截器、跨域等

controller层只做参数接收和结果返回,service层写业务逻辑,mapper只写数据库操作。这种三层的结构对任何管理系统都适用。别把业务逻辑写在controller里,也别在大伙儿都能看到的代码里随手画饼——分层清楚,代码才讲得明白。

3.2 统一返回结果和异常处理

前后端对接的时候,最忌讳就是各写各的。前端的规范是拿JSON,后端的接口返回格式如果不统一,前端解析就很痛苦。我这里定义了一个Result类,所有接口都返回这个结构:

public class Result<T> { private Integer code; // 200成功,500失败 private String message; // 提示信息 private T data; // 数据 }

比如登录成功后返回:

{ "code": 200, "message": "操作成功", "data": { "token": "xxx", "userInfo": { "id": 1, "username": "admin", "role": "ADMIN" } } }

有同学问为什么不用SpringBoot默认的返回结构,非要对Java做一层包装。答案很简单:默认返回结构里所有的字段都平铺在一起,错误处理混乱,前端拿到手要多做很多判断。统一Result结构之后,前端只需判断code字段。这就是所谓的“约定优于配置”。

异常处理用@RestControllerAdvice统一拦截,业务异常抛自定义异常类,系统异常返回通用错误信息。这一套必须要做,不然接口一报错就返回500的默认页面,前端看着莫名其妙。

3.3 登录认证:用拦截器做Token校验

这个部分是很多课设项目的重灾区。很多代码是登录成功之后存一下session,然后每个Controller里判断session是否为空。这么做本身没问题,但前后端分离的项目用session,会出现跨域带不上Cookie的问题。所以我换成了Token方案。

用户登录成功后,我用UUID生成一个Token,把Token作为key存在Redis里(也可以用内存Map),value是用户ID。请求进来的时候,拦截器从请求头拿到Token,去Redis查一下,查到了就放行,查不到就返回401。退出登录的时候把Redis里的Token删掉。

为什么不选JWT?因为JWT要考虑过期时间、密钥管理、刷新策略,课设项目里容易把自己绕晕。用Redis,实现简单、逻辑也容易讲清楚。如果环境里没有Redis,用一个ConcurrentHashMap存Token也能顶,但真实开发还是建议上Redis。

拦截器的核心代码大概长这样:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); // 查Redis或者Map验证token if (token == null || !validToken(token)) { response.setStatus(401); return false; } return true; } }

权限方面,我在controller的接口上用一个自定义注解,比如@RequireRole("ADMIN"),拦截器里先验证登录再验证角色。这样管理员接口和普通用户接口就能区分开。比如入库、供应商管理这些接口只有管理员能用,销售订单查询收银员也能用。

3.4 药品入库和出库的事务处理

进销存系统最核心的就是事务。一张入库单提交,要同时更新主表、明细表、药品库存,这三步任何一个失败,之前的操作就要回滚,否则就会出现乱账。

在SpringBoot里,我直接在service方法上加@Transactional注解:

@Transactional(rollbackFor = Exception.class) public void createPurchaseOrder(PurchaseOrderDTO dto) { // 1. 生成单号 // 2. 插入主表 // 3. 遍历明细,插入明细表 // 4. 逐条更新药品库存 }

有个容易忽略的细节:@Transactional默认只回滚RuntimeException,如果在业务代码里手动catch了Exception并吞掉了,事务是不会回滚的。所以我习惯在注解上显式写明rollbackFor = Exception.class,并且不在事务方法内部自己吞异常。

3.5 MyBatis动态SQL:多条件查询怎么写

药品列表查询这地方,大概率会用到多条件组合查询:按药品名称模糊查、按分类查、按库存状态查(低于库存下限的、高于上限的)。MyBatis的动态SQL就派上用场了。

<select id="findByCondition" resultType="com.xxx.pharmacy.entity.Medicine"> SELECT * FROM medicine <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="typeId != null"> AND type_id = #{typeId} </if> <if test="stockStatus == 'LOW'"> AND stock_quantity &lt; stock_min </if> <if test="stockStatus == 'HIGH'"> AND stock_quantity &gt; stock_max </if> </where> ORDER BY create_time DESC </select>

这里没写死SQL,用if标签动态拼接,条件为空就跳过,多条条件用where标签自动处理AND的拼接问题。比在Java代码里用StringBuilder去拼SQL干净太多,也安全太多,防SQL注入是配置参数绑定的天然属性。

4. 前端Vue实现:从页面到接口联调

4.1 前端项目结构和路由设计

前端用的是Vue2+Vuex+Vue Router+ElementUI的组合。项目结构如下:

src ├── api # 接口请求封装 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex状态管理 ├── views # 页面 │ ├── Login.vue │ ├── Layout.vue │ ├── medicine │ ├── stock │ ├── sale │ └── report └── utils # 工具函数,比如axios封装

路由分为两种,一种是公开路由,比如登录页;另一种是需要登录才能访问的路由,放到一个带Layout壳的父路由下面。Layout是一个侧边栏+头部+主内容区的框架页面,里面用router-view渲染子页面。

菜单是根据角色动态生成的。登录成功后拿到角色信息,前端根据角色渲染对应的菜单项。管理员看到全部菜单,收银员只看到销售和查询相关的。这个逻辑写在前端路由守卫里面:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })

4.2 axios封装与拦截器

前端所有请求都走axios,我在utils/request.js里做了统一封装:

const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElementUI.Message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )

这一步做完之后,所有的页面都只是调用api函数拿数据,不需要关心token怎么带上、错误怎么提示,非常省事。这里有个前端细节,跨域分两种场景:开发环境用Vue CLI的proxy代理,把请求转发到后端;生产环境把前端打包后的dist目录直接放到Nginx里,用Nginx配置转发到后端。开发和生产两个场景的跨域处理方式不一样,别搞混。

4.3 核心页面实现:药品管理、入库、销售页面

药品管理页面是个标准表格页面:顶部是搜索栏,中间是表格,右侧是操作按钮(编辑、删除),底部有分页。用ElementUI的el-table加上el-pagination就完成了。

入库页面稍微麻烦点。页面结构是:上方选择供应商,中间是药品明细的列表,可以往列表里添加多行药品,每行选择药品、填数量、算金额,底部显示总金额,提交之后调后端接口。这个页面的核心难度在“选择药品”这一步:需要一个弹窗,里面是药品列表,搜索后选中,选中后自动回填单价并锁定。

销售页面和入库页面类似,多了收银计算的功能:选择药品、数量后自动计算总价,收款之后打印小票(如果不需要打印这个功能,至少得有一个订单详情页)。

前端Vue有一个要点:不要在模板里写太复杂的逻辑,不该用computed的时候别硬凑,多用methods和computed合理分工。销售单总金额这种依赖于明细列表的计算属性,用computed会很优雅:

computed: { totalAmount() { return this.orderItems.reduce((sum, item) => sum + item.quantity * item.salePrice, 0) } }

4.4 权限菜单的动态渲染

我用了最简单直接的方式:登录返回的用户数据里带role字段,前端有一个菜单配置数组,每个菜单项都带一个roles属性,比如:

const menuList = [ { path: '/medicine', title: '药品管理', icon: 'el-icon-medicine', roles: ['ADMIN', 'PHARMACIST'] }, { path: '/purchase', title: '入库管理', icon: 'el-icon-box', roles: ['ADMIN'] }, { path: '/sale', title: '销售管理', icon: 'el-icon-shopping-cart', roles: ['ADMIN', 'CASHIER'] } ]

渲染菜单时用当前角色过滤一下,就能实现“管理员看到全部菜单,收银员只看到销售相关”的效果。这里不整那些复杂的动态路由和按钮级权限方案,菜单滤一遍就对了,简单好讲,答辩的时候也说得清楚。

5. 环境搭建与完整运行流程

5.1 环境准备清单

跑这套项目需要准备的工具如下:

  • JDK 1.8(安装后配JAVA_HOME环境变量)
  • MySQL 5.7或8.0(8.0以上要注意驱动兼容性问题)
  • Maven 3.6+(IDEA内置了Maven可以不用单独装)
  • Node.js 14+(前端编译需要)
  • IDEA或Eclipse(IDEA用起来舒服很多,社区版也够用)

我在实际演示的时候发现,很多同学卡在环境问题上占了整整两天。最典型的坑是:JDK版本装成了17,SpringBoot 2.7依赖的某些库在JDK17上运行有坑,要么换JDK8,要么升级SpringBoot 3.x。为了避免这种事儿,建议全部按我上面的版本来,不要追新。

5.2 后端启动步骤

  1. 用IDEA打开后端项目,等Maven把依赖下载完。
  2. 在MySQL里执行init.sql,建库建表导数据。
  3. 修改application.yml里的数据库连接信息,包括地址、端口、用户名、密码。
  4. 运行Application类的main方法。
  5. 打开浏览器访问http://localhost:8080/api/xxx,能看到JSON数据说明后端起来了。

这里有个测试小技巧:后端启动之后,先用Swagger或者Postman调一下接口验证,别急着联调前端。我项目里集成了Swagger(springfox),启动后访问http://localhost:8080/swagger-ui.html就能看接口文档,方便自测也方便给老师演示。

5.3 前端启动步骤

  1. 用IDEA或VSCode打开前端项目。
  2. 在终端执行npm install安装依赖(没配国内镜像的同学会等到怀疑人生,建议提前配置npm淘宝镜像)。
  3. 执行npm run serve启动开发服务器。
  4. 访问http://localhost:8081,默认端口由vue.config.js里的devServer配置决定。

前端开发服务器的proxy配置要指向后端地址:

devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这里要重点提一嘴,很多同学在这个问题上栽跟头:开发环境前端跑在8081,后端跑在8080,如果不配代理,前端请求后端接口必然跨域,浏览器直接给你拦下来。配了代理之后,前端请求的/api路径会被转发到后端的8080端口,问题就没了。

5.4 打包部署

调试完毕要交付的时候,前后端分开打包。后端用Maven的package命令打jar包,前端用npm run build把Vue项目编译成静态文件,生成的dist目录扔给Nginx托管,Nginx的配置大概是这样:

server { listen 80; server_name localhost; location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

try_files那行很关键,Vue Router用history模式时,刷新页面如果没配try_files,会出现404,因为Nginx默认找不到前端路由对应的物理文件。

6. 常见问题与排查技巧实录

6.1 依赖下载慢、下载失败

Maven和npm的依赖下载慢是每个做JavaWeb项目的都会遇到的问题。

排查方法:先确认是不是网络问题,然后配置国内镜像源。Maven在settings.xml里加阿里云镜像:

<mirror> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror>

npm设置镜像:

npm config set registry https://registry.npmmirror.com

依赖一直下载不下来还有一个原因是IDEA的Maven配置可能指向了错误的仓库,检查一下IDEA里Maven的Settings路径对不对。

6.2 数据库连接报错

最常见的报错是Access denied for user,说明用户名密码不对,或者是没有这个账号的远程访问权限。另外MySQL 8.0以上的驱动类名变成com.mysql.cj.jdbc.Driver,如果还在用老驱动会报ClassNotFoundException。

我还遇到过一个很隐蔽的问题:MySQL连接串没指定时区,连接报错Communications link failure,在url后面加上serverTimezone=Asia/Shanghai就能解决。

6.3 前端运行时跨域报错

报错信息通常是"Access-Control-Allow-Origin"之类的字样。解决方法有两个层面:

  • 后端加上全局CORS配置(@CrossOrigin注解或者CorsFilter),这是开发环境临时方案。
  • 更推荐的方式是前端开代理,通过devServer的proxy把请求转发到后端,生产环境用Nginx做反向代理。

我建议优先用代理方案,后端的CORS配置在生产环境里往往是安全隐患。如果代理配了还报跨域,检查一下前端请求的baseURL是不是/api开头,以及proxy里的target地址是不是写对了。

6.4 登录后访问接口返回401

Token失效或者Token没传成功。最常见的原因是拦截器从请求头取token的key跟前端设置的不一致。前端headers里用的Authorization,后端拦截器里写的request.getHeader("Authorization"),这在真实项目里有一个坑——如果前后端放在不同域名下,浏览器会先发起一次OPTIONS预检请求,OPTIONS请求是没有带业务头的,后端拦截器直接把OPTIONS请求放行才稳妥。

6.5 药品入库后库存不对

这个问题的根源往往是事务没生效,或者库存更新逻辑写错了。排查思路是:

  • 看入库接口有没有加@Transactional
  • 看service是不是被一个没有被Spring管理的外部方法调用(Spring事务是代理机制,自调用会导致事务失效)
  • 看数据库里库存字段有没有被其他逻辑重复更新

如果自己排查不出来,有个笨办法:在更新库存后往日志里print一下SQL和结果,看执行了几次更新,值是什么。

7. 避坑总结与扩展建议

7.1 做课设项目最容易踩的坑

很多人做课设项目,最后代码跑不起来或者答辩被问倒,基本逃不出这几个原因:

  • 只关注“代码能不能跑”,不关注“逻辑对不对”。登录能进去,界面能打开,就以为万事大吉,结果库存对不上账、金额算错、权限随便穿墙——答辩老师最喜欢在这类地方追问。
  • 数据库表结构设计得太随意,字段类型乱用,没有主外键关系,后期写代码各种别扭。其实建表之前花半小时画一张关系图,后面能省三天的返工时间。
  • 关键操作没有事务保护,比如入库、下单这种多表操作,中途报错就产生脏数据。面试一问“没有事务?”,那印象分就没了。

代码细节方面,不该出现的问题包括:密码明文存储、接口不校验参数、删除操作是物理删除没有软删除标识。这些虽然不是课设的硬性要求,但每次被问到都会加分。

7.2 项目还能怎么扩展

这套系统跑通之后,如果想再往上拔一层,有几个方向可以扩展:

  • 引入Spring Security + JWT做更规范的认证授权,能把权限这块讲得更深。
  • 用Redis缓存热点数据,比如药品列表、库存信息,减少数据库压力。
  • 接入ECharts做更丰富的统计图表,销售趋势、药品销售排行榜。
  • 增加药品批次管理,同一药品不同批号分开管理库存和生产日期。
  • 对接扫码枪,药品入库/出库直接用条码扫描,减少手工录入错误。

这些扩展点选一个合适的做到项目里,在答辩的时候会是亮点。要不说“做了”,得说清楚怎么做的、解决了什么问题,才有效果。

7.3 写操作记录还是写业务逻辑

最后多聊一句。管理系统这类项目最核心的不是界面好看,而是数据准确、流程顺畅。写代码的时候,要把重点放在核心链路的完整性和异常处理上,比如:库存扣减会不会变负数?入库单重复提交会不会产生重复库存?药品被删除之后历史订单怎么处理?这些问题想透了,代码怎么写都有底气。

课设和毕设的评分,很大程度看答辩现场。与其把大量时间花在调CSS样式上,不如把核心链路跑通,把事务、权限、异常处理这几个技术点吃透。代码是自己一行一行写出来的,老师一问细节能脱口而出,这个项目就稳了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询