KiriAky 107 46c242c772 添加图表数据展示功能
- 后端新增/chart-data接口用于获取图表所需数据
- 实现近6个月出入库金额统计和药品分类占比功能
- 前端集成echarts图表组件展示数据可视化
- 添加月度出入库柱状图和药品分类饼图
- 优化仪表盘界面布局和样式
2026-07-08 09:11:23 +08:00
2026-07-08 09:11:23 +08:00
2026-07-08 09:11:23 +08:00
2026-07-07 10:11:51 +08:00

药品管理系统 (MedicineManagerSystem)

基于 Vue 3 + Spring Boot 的全栈药品进销存管理系统,支持采购入库、库存管理、销售/领用出库、效期预警、供应商管理及 RBAC 权限控制。


技术栈

类别 技术 版本
前端框架 Vue 3 (Composition API) 3.5+
构建工具 Vite 8.x
UI 组件库 Element Plus 2.x
路由 / 状态 Vue Router 4 / Pinia 2 4.x / 2.x
HTTP / 样式 Axios 1.x / Sass 1.x
后端框架 Spring Boot (Web + Security + Validation) 4.1.0
JDK Java 21 LTS
ORM MyBatis-Plus 3.5.11
安全认证 Spring Security + JWT (jjwt 0.13.0)
数据库 MySQL 8.0 (JDBC + HikariCP)
缓存 Redis 7 + Jedis可选无连接自动跳过
工具库 Hutool 5.8 / Lombok
构建 / 部署 Maven 3.9+ / Docker + Docker Compose

系统架构

graph TB
    Client["🖥️ 浏览器<br/>Vue 3 + Element Plus SPA"]

    subgraph Backend["☕ Spring Boot 4.1.0"]
        Security["🔐 Spring Security + JWT Filter<br/>无状态认证"]
        Controller["📡 Controller Layer<br/>REST API · 参数校验 · 统一响应格式 Result-T"]
        Service["⚙️ Service Layer<br/>业务逻辑 · 事务管理"]
        Mapper["🗄️ MyBatis-Plus Mapper<br/>分页 · 逻辑删除 · 自动填充"]
    end

    DB["🐬 MySQL 8.0<br/>InnoDB · utf8mb4"]
    Cache["⚡ Redis 7<br/>可选 · 自动降级"]

    Client -->|"HTTPS + JWT Token"| Security
    Service --> Cache
    Security --> Controller
    Controller --> Service
    Service --> Mapper
    Mapper -->|"JDBC"| DB

请求响应流程

sequenceDiagram
    participant F as 🖥️ Vue 前端
    participant A as Axios 拦截器
    participant SF as JWT Filter
    participant C as Controller
    participant SV as Service
    participant M as Mapper
    participant DB as MySQL

    F->>A: 发起 API 请求
    A->>A: 自动注入 JWT Token<br/>到 Authorization Header
    A->>SF: HTTP Request (Bearer Token)

    alt Token 有效
        SF->>C: 放行请求
        C->>C: @Valid 参数校验
        C->>SV: 调用业务方法
        SV->>M: 调用数据访问
        M->>DB: 执行 SQL
        DB-->>M: 返回结果集
        M-->>SV: 返回实体对象
        SV-->>C: 返回处理结果
        C-->>A: "Result<T> JSON"
        A->>A: 响应拦截器处理<br/>(统一错误提示 / Token 刷新)
        A-->>F: 展示数据
    else Token 过期或无效
        SF-->>A: 401 Unauthorized
        A->>A: 尝试 RefreshToken
        alt 刷新失败
            A->>F: 跳转登录页
        end
    else 权限不足
        SF-->>A: 403 Forbidden
        A-->>F: 提示"权限不足"
    end

RBAC 权限模型

graph LR
    U["👤 User"] -->|N:M| UR["user_role"]
    UR -->|N:M| R["🔑 Role"]
    R -->|N:M| RM["role_menu"]
    RM -->|N:M| M["📋 Menu"]

    M --> D["📁 目录 type=1<br/>侧边栏分组"]
    M --> P["📄 菜单 type=2<br/>页面路由"]
    M --> B["🔘 按钮 type=3<br/>permission_code"]

    subgraph 三层权限
        L1["路由守卫"] -.-> P
        L2["v-permission 指令"] -.-> B
        L3["@PreAuthorize"] -.-> B
    end

认证流程

sequenceDiagram
    participant U as 👤 用户
    participant F as 🖥️ Vue 前端
    participant B as ☕ Spring Boot
    participant DB as 🐬 MySQL

    rect rgb(240, 248, 255)
        Note over U,DB: === 登录阶段 ===
        U->>F: 输入用户名 + 密码
        F->>B: POST /api/auth/login<br/>{username, password}
        B->>DB: 查询用户信息
        DB-->>B: 返回用户实体
        B->>B: BCrypt.matches(password, hash)
        alt 验证成功
            B->>B: 生成 AccessToken (有效期 24h)
            B->>B: 生成 RefreshToken (有效期 7天)
            B-->>F: { token, refreshToken, userInfo, menus }
            F->>F: localStorage.setItem('token', ...)
            F-->>U: 跳转首页,渲染菜单
        else 验证失败
            B-->>F: 401 "用户名或密码错误"
            F-->>U: ElMessage.error 提示
        end
    end

    rect rgb(255, 248, 240)
        Note over U,DB: === 正常请求阶段 ===
        F->>B: GET /api/medicines<br/>Authorization: Bearer <AccessToken>
        B->>B: JwtFilter 解析 Token<br/>提取 userId + permissions
        B->>B: SecurityContextHolder 设置认证信息
        B-->>F: Result<Page<Medicine> >
    end

    rect rgb(255, 240, 240)
        Note over U,DB: === Token 过期刷新 ===
        F->>B: POST /api/auth/refresh<br/>{ refreshToken }
        B->>B: 校验 RefreshToken 有效性
        alt RefreshToken 有效
            B->>B: 签发新 AccessToken
            B-->>F: { token: "newToken" }
            F->>F: 更新 localStorage
        else RefreshToken 过期
            B-->>F: 401
            F->>F: 清空 Token跳转登录页
        end
    end

数据库设计

erDiagram
    medicine_category ||--o{ medicine : "分类"
    medicine ||--o{ stock : "库存批次"
    medicine ||--o{ stock_in_detail : ""
    medicine ||--o{ stock_out_detail : ""
    warehouse ||--o{ stock : ""
    warehouse ||--o{ stock_in_record : ""
    warehouse ||--o{ stock_out_record : ""
    supplier ||--o{ stock_in_record : ""
    customer ||--o{ stock_out_record : ""
    stock_in_record ||--o{ stock_in_detail : ""
    stock_out_record ||--o{ stock_out_detail : ""
    user ||--o{ stock_in_record : "操作人"
    user ||--o{ stock_out_record : "操作人"
    user ||--o{ user_role : ""
    role ||--o{ user_role : ""
    role ||--o{ role_menu : ""
    menu ||--o{ role_menu : ""
    menu ||--o{ menu : "父子菜单"

    medicine_category { bigint id PK "分类ID" varchar name "名称" varchar code UK "编码" }
    medicine { bigint id PK "药品ID" varchar name "通用名称" varchar specification "规格" varchar manufacturer "厂家" varchar approval_number UK "批准文号" decimal retail_price "零售价" decimal wholesale_price "批发价" int shelf_life "有效期(月)" int warning_stock "预警阈值" }
    supplier { bigint id PK "供应商ID" varchar name "名称" varchar contact_person "联系人" varchar phone "电话" tinyint status "状态" }
    customer { bigint id PK "客户ID" varchar name "名称" varchar phone "电话" varchar type "类型" }
    warehouse { bigint id PK "仓库ID" varchar name "名称" varchar code UK "编码" }
    stock { bigint id PK "库存ID" bigint medicine_id FK "药品" bigint warehouse_id FK "仓库" varchar batch_no "批号" int quantity "数量" decimal cost_price "成本价" date expiry_date "有效期至" }
    stock_in_record { bigint id PK "入库单ID" varchar record_no UK "单号" bigint supplier_id FK "供应商" decimal total_amount "总金额" date in_date "入库日期" tinyint status "0待审1已入库2已取消" }
    stock_in_detail { bigint id PK bigint record_id FK bigint medicine_id FK varchar batch_no int quantity decimal cost_price "进价" decimal total_price "小计" date expiry_date }
    stock_out_record { bigint id PK "出库单ID" varchar record_no UK "单号" bigint customer_id FK "客户" decimal total_amount "总金额" date out_date "出库日期" tinyint out_type "1销售2领用3报损4退货" tinyint status "0待审1已出库2已取消" }
    stock_out_detail { bigint id PK bigint record_id FK bigint medicine_id FK varchar batch_no int quantity decimal unit_price "单价" decimal total_price "小计" }
    user { bigint id PK "用户ID" varchar username UK "用户名" varchar password "BCrypt" varchar real_name "姓名" varchar phone "手机" tinyint status "状态" }
    role { bigint id PK "角色ID" varchar name "名称" varchar code UK "编码" }
    user_role { bigint id PK bigint user_id FK bigint role_id FK }
    menu { bigint id PK "菜单ID" bigint parent_id "父ID" varchar name "名称" tinyint type "1目录2菜单3按钮" varchar path "路由" varchar icon "图标" varchar permission_code UK "权限编码" }
    role_menu { bigint id PK bigint role_id FK bigint menu_id FK }

功能模块

模块 功能
🔐 认证 登录 / 注册 / JWT 刷新 / 退出
📊 仪表盘 4 项统计卡片 · 效期预警<30天红 · 30-90天橙 · 最近出入库
💊 药品管理 CRUD · 分类树管理 · 按名称/批准文号/分类搜索 · 分页
📦 库存管理 批次跟踪 · 效期颜色标记 · 库存盘点
📥 入库管理 新建入库单(供应商+动态明细行) · 审核通过自动增加库存 · 驳回
📤 出库管理 新建出库单(客户+库存批次选择) · 审核通过 FIFO 扣减库存 · 驳回
🏢 供应商 CRUD · 启用/停用开关
👥 客户 CRUD · 类型(个人/医院/药店/诊所)
👤 用户管理 CRUD · 角色分配
🔑 角色管理 CRUD · 权限树分配
📋 菜单管理 菜单树 CRUD · 三级(目录/菜单/按钮)

核心业务流程

入库审核

sequenceDiagram
    participant F as 前端
    participant B as 后端
    participant DB as MySQL

    Note over F,DB: 1. 创建入库单
    F->>B: POST /api/stock-in<br/>{ supplierId, details: [...] }
    B->>DB: INSERT stock_in_record (status=0 待审核)
    B->>DB: INSERT stock_in_detail (明细)
    B-->>F: 入库单已创建

    Note over F,DB: 2. 审核通过
    F->>B: PUT /api/stock-in/{id}/status<br/>{ status: 1 }
    B->>DB: 查同药品+同批号库存
    alt 库存已存在
        B->>DB: UPDATE stock SET quantity += N
    else 新批次
        B->>DB: INSERT stock (新批次)
    end
    B->>DB: UPDATE status=1
    B-->>F: 审核通过 · 库存已更新

出库审核

sequenceDiagram
    participant F as 前端
    participant B as 后端
    participant DB as MySQL

    Note over F,DB: 1. 创建出库单
    F->>B: POST /api/stock-out<br/>{ customerId, details: [...] }
    B->>DB: INSERT stock_out_record (status=0 待审核)

    Note over F,DB: 2. 审核通过
    F->>B: PUT /api/stock-out/{id}/status<br/>{ status: 1 }
    B->>DB: 查可用库存 (FIFO 按效期升序)
    alt 库存充足
        B->>DB: UPDATE stock SET quantity -= N
        B->>DB: UPDATE status=1
        B-->>F: 审核通过 · 库存已扣减
    else 库存不足
        B-->>F: 错误: 库存不足,需要 X可用 Y
    end

快速开始

方式一Docker Compose推荐

# 1. 配置环境变量
cd backend
cp .env.example .env
# 编辑 .env 设置 DB_PASSWORD 和 JWT_SECRET

# 2. 一键启动MySQL + Redis + 后端 + 前端)
docker-compose up -d

# 3. 访问
open http://localhost

首次启动 MySQL 会自动执行 init.sql 建表和数据。Redis 不可用时后端自动降级跳过缓存。

方式二:手动启动

环境要求

  • JDK 21+ · Node.js 18+ · MySQL 8.0+ · Maven 3.9+

1. 初始化数据库

mysql -u root -p < backend/src/main/resources/database/init.sql

2. 配置后端环境变量

cd backend
cp .env.example .env

backend/.env 中填写:

DB_HOST=localhost
DB_PORT=3306
DB_NAME=medicine_manager
DB_USERNAME=root
DB_PASSWORD=你的数据库密码
JWT_SECRET=你的256位Base64密钥

3. 启动后端

cd backend
./mvnw spring-boot:run -DskipTests

后端运行在 http://localhost:8080,启动时会自动加载 .env

4. 启动前端

cd frontend
npm install
npm run dev

前端运行在 http://localhost:5173API 请求自动代理到后端

5. 登录

用户名 密码 角色 权限范围
admin admin123 超级管理员 全部
warehouse01 admin123 仓库管理员 出入库 + 库存 + 供应商客户查看
staff01 admin123 普通员工 仪表盘 + 只读查看

项目结构

MedicineManagerSystem/
├── README.md
├── .env.example
├── docker-compose.yml              # Docker 编排
├── projectDesigning.md             # 详细架构设计文档
│
├── frontend/                       # Vue 3 SPA
│   ├── Dockerfile
│   ├── nginx.conf
│   └── src/
│       ├── api/             (11)   # API 模块
│       ├── directives/      (1)    # v-permission
│       ├── layout/          (1)    # MainLayout
│       ├── router/          (1)    # 路由 + 动态菜单
│       ├── stores/          (2)    # Pinia
│       ├── styles/          (1)    # SCSS
│       ├── utils/           (2)    # Axios · Redis 缓存
│       └── views/           (16)   # 页面组件
│
└── backend/                        # Spring Boot
    ├── Dockerfile
    └── src/main/
        ├── java/com/kronecker/backend/
        │   ├── common/      (4)    # Result · 异常
        │   ├── config/      (3)    # Security · MP · CORS
        │   ├── controller/  (12)   # REST API
        │   ├── dto/         (4)    # 请求对象
        │   ├── entity/      (15)   # 数据库实体
        │   ├── mapper/      (15)   # MP Mapper
        │   ├── security/    (3)    # JWT
        │   ├── service/     (23)   # 业务逻辑
        │   └── utils/       (1)    # Redis 缓存
        └── resources/
            ├── application.yml
            └── database/init.sql   # 建表 + 种子数据

API 端点

模块 方法 路径 权限
认证 POST /api/auth/login 公开
认证 POST /api/auth/register 公开
认证 POST /api/auth/refresh 公开
认证 GET /api/auth/userinfo 登录
仪表盘 GET /api/dashboard/statistics 登录
仪表盘 GET /api/dashboard/expiry-warning 登录
药品 GET POST PUT DELETE /api/medicines[/{id}] medicine:*
分类 GET POST PUT DELETE /api/categories[/{id}] category:*
供应商 GET POST PUT DELETE /api/suppliers[/{id}] supplier:*
客户 GET POST PUT DELETE /api/customers[/{id}] customer:*
库存 GET /api/stocks stock:list
入库 GET POST /api/stock-in stock-in:*
入库 PUT /api/stock-in/{id}/status stock-in:audit
出库 GET POST /api/stock-out stock-out:*
出库 PUT /api/stock-out/{id}/status stock-out:audit
用户 GET POST PUT DELETE /api/users[/{id}] user:*
角色 GET POST PUT DELETE /api/roles[/{id}] role:*
菜单 GET POST PUT DELETE /api/menus[/{id}] menu:*
菜单 GET /api/menus/tree 登录
Description
No description provided
Readme 336 KiB
Languages
Java 47.1%
Vue 42.4%
JavaScript 8.3%
SCSS 1.4%
Dockerfile 0.6%
Other 0.2%