☰
广西某二甲医院办公室信息系统的设计与开发
2026/9/29 6:14:48 网站建设 项目流程

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片!

1. 项目背景与意义

随着医疗信息化建设的不断深入,二级甲等(二甲)医院在日常行政办公中仍普遍存在流程繁琐、信息孤岛、文件流转效率低等问题。办公室作为医院行政运转的中枢,承担着公文收发、会议管理、值班安排、通知公告、档案归档等大量事务性工作。传统的人工登记、纸质流转和电话通知方式不仅耗时费力,还容易出现信息遗漏和追溯困难。

设计并开发一套面向二甲医院办公室的信息系统,具有以下现实意义:

  • 提升办公效率:将公文流转、会议通知、值班排班等流程线上化,减少人工传递环节,缩短审批周期。
  • 规范管理流程:通过统一的信息化平台固化工作流程,明确各岗位职责,减少人为随意性。
  • 保障信息安全:建立分级权限管理和操作日志,确保敏感行政文件仅限授权人员查阅,满足医院信息安全合规要求。
  • 降低运营成本:减少纸张、打印和通信费用,同时降低文件归档和检索的人力成本。
  • 辅助管理决策:通过对公文量、会议频次、值班记录等数据的统计分析,为院办管理提供数据支撑。

2. 系统需求分析

2.1 用户角色分析

根据医院办公室的实际业务分工,系统用户可分为以下四类角色:

角色主要职责核心权限
系统管理员系统配置、用户管理、权限分配全部模块的配置与维护权限
办公室主任公文审批、会议审批、综合管理审批、统计、导出、归档
办公文员公文起草、会议通知、值班排班起草、提交、编辑、查询
普通职工查看通知、接收会议邀请、查看值班表只读查询、确认反馈

2.2 功能需求

系统核心功能模块包括:

  • 公文管理:支持公文的起草、审核、签发、分发、归档全流程管理,记录流转痕迹。
  • 会议管理:会议室预约、会议通知发布、参会回执、会议纪要归档。
  • 值班管理:值班排班、值班调换申请、值班提醒与记录查询。
  • 通知公告:院内通知的发布、置顶、撤回与已读统计。
  • 档案管理:行政文件的分类归档、关键词检索、借阅登记。
  • 系统管理:用户管理、角色权限、操作日志、数据备份。

3. 技术栈选型

3.1 总体架构

系统采用 B/S(浏览器/服务器)架构,前后端分离开发,部署于医院内网服务器。总体架构分为表现层、应用层、服务层和数据层四个层次。

flowchart TD A[浏览器客户端] --> B[Nginx 反向代理] B --> C[前端应用 Vue3 + Element Plus] C --> D[后端服务 Spring Boot] D --> E[业务模块: 公文/会议/值班/通知/档案] E --> F[数据访问 MyBatis-Plus] F --> G[(MySQL 数据库)] D --> H[Redis 缓存] D --> I[MinIO 文件存储]

3.2 后端技术栈

技术版本用途说明
JavaJDK 17开发语言,长期支持版本
Spring Boot3.2.x应用框架,快速构建 RESTful 服务
MyBatis-Plus3.5.xORM 框架,简化数据库操作
MySQL8.0关系型数据库,存储业务数据
Redis7.x缓存会话、验证码、热点数据
MinIO最新稳定版对象存储,保存附件和公文扫描件
Spring Security6.x认证与授权框架
JWT0.12.x无状态令牌认证

3.3 前端技术栈

技术版本用途说明
Vue3.4.x前端渐进式框架
Vite5.x构建工具,开发体验好
Element Plus2.7.xUI 组件库,适配后台管理系统
Pinia2.x状态管理
Axios1.xHTTP 请求库
ECharts5.x数据可视化图表

4. 数据库设计

4.1 核心表结构

系统共设计 12 张业务表,以下列出三个核心表的结构。

用户表(sys_user)

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT '加密密码', real_name VARCHAR(50) NOT NULL COMMENT '真实姓名', department VARCHAR(100) COMMENT '所属科室', phone VARCHAR(20) COMMENT '联系电话', role_id BIGINT COMMENT '角色ID', status TINYINT DEFAULT 1 COMMENT '状态: 1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) COMMENT '系统用户表';

公文表(doc_document)

CREATE TABLE doc_document ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', doc_no VARCHAR(50) NOT NULL COMMENT '公文编号', title VARCHAR(200) NOT NULL COMMENT '公文标题', content TEXT COMMENT '公文正文', doc_type VARCHAR(20) COMMENT '公文类型: 通知/请示/报告/批复', status TINYINT DEFAULT 0 COMMENT '状态: 0草稿 1待审 2已签发 3已归档', creator_id BIGINT COMMENT '起草人ID', approver_id BIGINT COMMENT '审批人ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', archive_time DATETIME COMMENT '归档时间' ) COMMENT '公文表';

会议表(mt_meeting)

CREATE TABLE mt_meeting ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', title VARCHAR(200) NOT NULL COMMENT '会议主题', location VARCHAR(100) COMMENT '会议地点', start_time DATETIME NOT NULL COMMENT '开始时间', end_time DATETIME COMMENT '结束时间', host_id BIGINT COMMENT '主持人ID', content TEXT COMMENT '会议内容', status TINYINT DEFAULT 0 COMMENT '状态: 0待召开 1已召开 2已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) COMMENT '会议表';

5. 核心功能实现

5.1 后端核心代码

公文审批流程控制

@Service public class DocumentService { @Autowired private DocumentMapper documentMapper; @Autowired private DocumentLogMapper logMapper; /** 提交公文审批 */ @Transactional(rollbackFor = Exception.class) public void submitForApproval(Long docId, Long approverId) { Document doc = documentMapper.selectById(docId); if (doc == null) { throw new BusinessException("公文不存在"); } if (!"草稿".equals(doc.getStatus())) { throw new BusinessException("当前状态不可提交审批"); } doc.setStatus("待审"); doc.setApproverId(approverId); documentMapper.updateById(doc); // 记录操作日志 DocumentLog log = new DocumentLog(); log.setDocId(docId); log.setAction("提交审批"); log.setOperatorId(SecurityUtils.getCurrentUserId()); logMapper.insert(log); } /** 审批公文 */ @Transactional(rollbackFor = Exception.class) public void approve(Long docId, boolean pass, String comment) { Document doc = documentMapper.selectById(docId); if (doc == null) { throw new BusinessException("公文不存在"); } if (!"待审".equals(doc.getStatus())) { throw new BusinessException("公文不在待审状态"); } doc.setStatus(pass ? "已签发" : "已驳回"); documentMapper.updateById(doc); DocumentLog log = new DocumentLog(); log.setDocId(docId); log.setAction(pass ? "审批通过" : "审批驳回"); log.setComment(comment); log.setOperatorId(SecurityUtils.getCurrentUserId()); logMapper.insert(log); } }

值班排班算法

@Service public class DutyScheduleService { @Autowired private DutyMapper dutyMapper; /** 自动生成月度值班表 采用轮询算法,尽量均衡分配值班次数 */ public List<DutySchedule> generateMonthlySchedule( List<Long> userIds, int year, int month) { List<DutySchedule> result = new ArrayList<>(); int daysInMonth = getDaysInMonth(year, month); int userCount = userIds.size(); if (userCount == 0) { throw new BusinessException("值班人员列表为空"); } // 记录每人当月已值班次数,用于均衡分配 Map<Long, Integer> dutyCount = new HashMap<>(); for (Long uid : userIds) { dutyCount.put(uid, 0); } for (int day = 1; day <= daysInMonth; day++) { // 选择当前值班次数最少的人员 Long selected = userIds.stream() .min(Comparator.comparingInt(dutyCount::get)) .orElseThrow(() -> new BusinessException("无法选择值班人员")); DutySchedule schedule = new DutySchedule(); schedule.setUserId(selected); schedule.setDutyDate(LocalDate.of(year, month, day)); schedule.setDutyType(isWeekend(year, month, day) ? "节假日" : "工作日"); result.add(schedule); dutyCount.put(selected, dutyCount.get(selected) + 1); } return result; } private boolean isWeekend(int year, int month, int day) { DayOfWeek dow = LocalDate.of(year, month, day).getDayOfWeek(); return dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY; } private int getDaysInMonth(int year, int month) { return YearMonth.of(year, month).lengthOfMonth(); } }

JWT 认证过滤器

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider tokenProvider; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && tokenProvider.validateToken(token)) { String username = tokenProvider.getUsername(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { String bearer = request.getHeader("Authorization"); if (bearer != null && bearer.startsWith("Bearer ")) { return bearer.substring(7); } return null; } }

5.2 前端核心代码

Axios 请求封装

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 15000 }) // 请求拦截器:附加 JWT 令牌 service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = Bearer ${token} } return config }, error => { return Promise.reject(error) }) // 响应拦截器:统一处理错误 service.interceptors.response.use(response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') ElMessage.warning('登录已过期,请重新登录') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) }) export default service

公文列表页面

<template> <div class="document-list"> <el-card> <div class="toolbar"> <el-input v-model="query.keyword" placeholder="搜索公文标题或编号" clearable style="width: 260px" @keyup.enter="loadData" /> <el-select v-model="query.status" placeholder="状态" clearable style="width: 140px"> <el-option label="草稿" value="草稿" /> <el-option label="待审" value="待审" /> <el-option label="已签发" value="已签发" /> <el-option label="已归档" value="已归档" /> </el-select> <el-button type="primary" @click="loadData">查询</el-button> <el-button type="success" @click="openCreateDialog">新建公文</el-button> </div> &lt;el-table :data="list" v-loading="loading" border stripe&gt; &lt;el-table-column prop="docNo" label="公文编号" width="140" /&gt; &lt;el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip /&gt; &lt;el-table-column prop="docType" label="类型" width="100" /&gt; &lt;el-table-column prop="status" label="状态" width="100"&gt; &lt;template #default="{ row }"&gt; &lt;el-tag :type="statusTagType(row.status)"&gt;{{ row.status }}&lt;/el-tag&gt; &lt;/template&gt; &lt;/el-table-column&gt; &lt;el-table-column prop="createTime" label="创建时间" width="170" /&gt; &lt;el-table-column label="操作" width="220" fixed="right"&gt; &lt;template #default="{ row }"&gt; &lt;el-button size="small" @click="viewDetail(row

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

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

立即咨询