医院病人信息管理系统Web版开发实战


文章目录
- 医院病人信息管理系统Web版开发实战
-
- 一、概述
- 二、项目结构搭建
-
- 2.1 Flask 项目结构原理
- 2.2 创建项目文件夹
- 2.3 初始化 Flask 应用
- 2.1 Flask 项目结构原理
- 三、路由设计
-
- 3.1 Flask 路由机制原理
- 3.2 定义核心路由
- 3.3 数据存储函数
- 3.1 Flask 路由机制原理
- 四、模板开发
-
- 4.1 Jinja2 模板引擎原理
- 4.2 基础模板设计
- 4.3 首页模板(index.html)
- 4.4 添加病人模板(add.html)
- 4.5 详情和编辑模板
- 4.1 Jinja2 模板引擎原理
- 未完待续。。。
一、概述
欢迎来到第十八章 “实战项目三:简易 Web 应用开发”。在前面的章节中,我们已经学习了 Python 编程基础、Flask 框架入门、HTML/CSS/JavaScript 等前端技术,以及数据处理等知识。今天我们将通过一个完整的项目 ——医院病人信息管理系统 Web 版,来综合运用所学知识,实现一个具有增删改查功能的 Web 应用。
本项目开发的五个核心环节:项目结构搭建、路由设计、模板开发、数据存储实现,以及功能测试与页面美化。通过这个项目,将掌握 Web 应用开发的完整流程。
二、项目结构搭建
2.1 Flask 项目结构原理
在开始编码之前,我们需要先了解 Flask 项目的标准结构。一个规范的 Flask 项目应该包含以下核心目录和文件:
hospital_patient_manager/
├── app/
│ ├── __init__.py # 应用初始化文件
│ ├── routes.py # 路由定义文件
│ └── models.py # 数据模型文件
├── templates/ # 存放HTML模板文件
├── static/ # 存放静态资源(CSS、JS、图片等)
│ ├── css/
│ ├── js/
│ └── img/
├── config.py # 配置文件
├── run.py # 应用启动文件
└── requirements.txt # 项目依赖包列表
这种结构遵循了模块化设计原则,将不同功能的代码分离,提高了代码的可维护性和可扩展性。其中:
- templates 文件夹专门存放 Jinja2 模板文件(HTML 文件),Flask 默认从此目录加载模板用于渲染动态网页
- static 文件夹存放静态资源文件,如 CSS 样式表、JavaScript 脚本和图像文件,通过
/static/URL 前缀访问
2.2 创建项目文件夹
现在我们开始创建项目结构。首先打开终端,执行以下命令:
1# 创建项目根目录 2mkdir hospital_patient_manager 3cd hospital_patient_manager 4 5# 创建app目录 6mkdir app 7touch app/__init__.py 8touch app/routes.py 9touch app/models.py 10 11# 创建templates和static目录 12mkdir templates 13mkdir static 14mkdir static/css 15mkdir static/js 16mkdir static/img 17 18# 创建配置文件和启动文件 19touch config.py 20touch run.py 21 22# 创建依赖文件 23touch requirements.txt 24
项目结构创建完成后,我们需要安装项目所需的依赖包。在requirements.txt中添加以下内容:
flask==2.2.3
pandas==2.0.3
openpyxl==3.1.2
然后执行命令安装依赖:
pip install -r requirements.txt
2.3 初始化 Flask 应用
在 app/`` ``__init__.py中编写代码,初始化 Flask 应用:
1from flask import Flask 2 3# 创建Flask应用实例 4app = Flask(__name__) 5 6# 加载配置 7app.config.from_object('config.Config') 8 9# 导入路由模块 10from app import routes 11
在config.py中定义配置类:
1class Config: 2 """项目配置类""" 3 SECRET_KEY = 'your-secret-key-here' # 用于表单安全 4 DEBUG = True # 开发环境开启调试模式 5
在run.py中编写启动代码:
1from app import app 2 3if __name__ == '__main__': 4 app.run() 5
三、路由设计
3.1 Flask 路由机制原理
Flask 的路由系统是其核心功能之一,负责将 URL 地址映射到相应的处理函数上。路由通过@app.route()装饰器定义,其工作原理如下:
- 路由匹配过程:当 Flask 应用接收到一个 HTTP 请求时,会根据请求的 URL 查找匹配的路由。这个过程通过 Flask 内部使用的 Werkzeug 库的路由系统完成。
- 装饰器机制:
@app.route()本质上是一个 “装饰器工厂”,接收 URL 路径参数,返回真正的装饰器函数,再由这个装饰器完成路由注册。 - 动态路由支持:Flask 支持动态 URL 参数,通过尖括号
<variable_name>定义,支持多种类型转换器(int、float、path、uuid 等)。 - HTTP 方法支持:可以通过 methods 参数指定路由支持的 HTTP 方法(GET、POST 等),实现 RESTful 风格的接口设计。
3.2 定义核心路由
根据医院病人信息管理系统的功能需求,我们需要定义以下路由:
1from flask import render_template, redirect, url_for, request, flash 2from app import app 3import pandas as pd 4import os 5 6# 病人信息存储文件 7DATA_FILE = 'patients.xlsx' 8 9# 首页路由 - 显示病人列表 10@app.route('/') 11def index(): 12 """显示病人列表""" 13 patients = load_patients() 14 return render_template('index.html', patients=patients) 15 16# 添加病人路由 - GET显示表单,POST处理提交 17@app.route('/add', methods=['GET', 'POST']) 18def add_patient(): 19 """添加病人信息""" 20 if request.method == 'POST': 21 # 获取表单数据 22 name = request.form['name'] 23 age = request.form['age'] 24 gender = request.form['gender'] 25 diagnosis = request.form['diagnosis'] 26 admission_date = request.form['admission_date'] 27 28 # 数据验证 29 if not name or not age or not gender or not diagnosis or not admission_date: 30 flash('所有字段都是必填项!', 'error') 31 return redirect(url_for('add_patient')) 32 33 try: 34 age = int(age) 35 except: 36 flash('年龄必须是整数!', 'error') 37 return redirect(url_for('add_patient')) 38 39 # 创建病人数据 40 patient = { 41 '姓名': name, 42 '年龄': age, 43 '性别': gender, 44 '诊断': diagnosis, 45 '入院日期': admission_date 46 } 47 48 # 保存到文件 49 save_patient(patient) 50 flash('病人信息添加成功!','success') 51 return redirect(url_for('index')) 52 53 return render_template('add.html') 54 55# 查看病人详情路由 56@app.route('/patient/<int:id>') 57def patient_detail(id): 58 """查看病人详细信息""" 59 patients = load_patients() 60 patient = patients[patients['ID'] == id].iloc[0] if len(patients) > 0 else None 61 return render_template('detail.html', patient=patient) 62 63# 修改病人信息路由 64@app.route('/edit/<int:id>', methods=['GET', 'POST']) 65def edit_patient(id): 66 """修改病人信息""" 67 patients = load_patients() 68 patient = patients[patients['ID'] == id].iloc[0] if len(patients) > 0 else None 69 70 if request.method == 'POST': 71 # 获取表单数据 72 name = request.form['name'] 73 age = request.form['age'] 74 gender = request.form['gender'] 75 diagnosis = request.form['diagnosis'] 76 admission_date = request.form['admission_date'] 77 78 # 数据验证 79 if not name or not age or not gender or not diagnosis or not admission_date: 80 flash('所有字段都是必填项!', 'error') 81 return redirect(url_for('edit_patient', id=id)) 82 83 try: 84 age = int(age) 85 except: 86 flash('年龄必须是整数!', 'error') 87 return redirect(url_for('edit_patient', id=id)) 88 89 # 更新病人信息 90 patients.loc[id-1, ['姓名', '年龄', '性别', '诊断', '入院日期']] = [name, age, gender, diagnosis, admission_date] 91 save_all_patients(patients) 92 flash('病人信息修改成功!','success') 93 return redirect(url_for('index')) 94 95 return render_template('edit.html', patient=patient) 96 97# 删除病人信息路由 98@app.route('/delete/<int:id>') 99def delete_patient(id): 100 """删除病人信息""" 101 patients = load_patients() 102 patients = patients[patients['ID'] != id] 103 save_all_patients(patients) 104 flash('病人信息删除成功!','success') 105 return redirect(url_for('index')) 106
3.3 数据存储函数
为了实现数据的持久化存储,我们需要编写文件操作函数:
1def load_patients(): 2 """加载病人数据""" 3 if not os.path.exists(DATA_FILE): 4 # 文件不存在,创建表头 5 df = pd.DataFrame(columns=['ID', '姓名', '年龄', '性别', '诊断', '入院日期']) 6 df.to_excel(DATA_FILE, index=False) 7 8 # 读取Excel文件 9 df = pd.read_excel(DATA_FILE, index_col=False) 10 11 # 如果ID列不存在或不连续,重新生成ID 12 if 'ID' not in df.columns or df['ID'].isna().any() or len(df) != df['ID'].max(): 13 df['ID'] = range(1, len(df) + 1) 14 15 return df 16 17def save_patient(patient): 18 """保存单个病人信息""" 19 df = load_patients() 20 21 # 生成新的ID 22 new_id = df['ID'].max() + 1 if len(df) > 0 else 1 23 patient['ID'] = new_id 24 25 # 添加到DataFrame 26 new_df = pd.DataFrame([patient]) 27 df = pd.concat([df, new_df], ignore_index=True) 28 29 # 保存到文件 30 df.to_excel(DATA_FILE, index=False) 31 32def save_all_patients(df): 33 """保存所有病人信息""" 34 # 重置索引并重新生成ID 35 df = df.reset_index(drop=True) 36 df['ID'] = range(1, len(df) + 1) 37 df.to_excel(DATA_FILE, index=False) 38
四、模板开发
4.1 Jinja2 模板引擎原理
Flask 使用 Jinja2 作为默认的模板引擎,它是一个基于 Python 语法的模板引擎,具有强大的模板功能和灵活的扩展性。Jinja2 的核心特性包括:
- 变量输出:使用
{{ variable }}语法输出变量值,支持基本数据类型和表达式运算。 - 控制结构:支持
{% if %}条件判断和{% for %}循环结构,用于动态控制页面内容。 - 模板继承:通过
{% extends %}和{% block %}标签实现模板继承,避免重复代码。 - 安全机制:默认启用自动转义机制,防止跨站脚本攻击(XSS)。
4.2 基础模板设计
首先创建base.html作为基础模板,定义页面的整体结构:
1<!DOCTYPE html> 2<html lang="zh-CN"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>{% block title %}医院病人信息管理系统{% endblock %}</title> 7 8 <!-- 引入Bootstrap CSS --> 9 <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css"> 10 11 <!-- 自定义样式 --> 12 <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> 13</head> 14<body> 15 <div class="container mt-3"> 16 <h1 class="text-center mb-4">医院病人信息管理系统</h1> 17 18 <!-- 显示提示信息 --> 19 {% with messages = get_flashed_messages(with_categories=true) %} 20 {% if messages %} 21 {% for category, message in messages %} 22 <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert"> 23 {{ message }} 24 <button type="button" class="close" data-dismiss="alert" aria-label="Close"> 25 <span aria-hidden="true">×</span> 26 </button> 27 </div> 28 {% endfor %} 29 {% endif %} 30 {% endwith %} 31 32 <!-- 内容区域 --> 33 {% block content %}{% endblock %} 34 </div> 35 36 <!-- 引入Bootstrap JS --> 37 <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script> 38 <script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script> 39 <script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script> 40 41 <!-- 自定义脚本 --> 42 <script src="{{ url_for('static', filename='js/script.js') }}"></script> 43</body> 44</html> 45
4.3 首页模板(index.html)

创建index.html模板,显示病人列表:
1{% extends "base.html" %} 2{% block title %}病人列表{% endblock %} 3{% block content %} 4 <div class="row mb-3"> 5 <div class="col-md-6"> 6 <a href="{{ url_for('add_patient') }}" class="btn btn-primary"> 7 <i class="fas fa-plus"></i> 添加病人 8 </a> 9 </div> 10 <div class="col-md-6 text-right"> 11 <form class="form-inline justify-content-end" method="GET" action="{{ url_for('search') }}"> 12 <input class="form-control mr-sm-2" type="search" placeholder="搜索病人" name="q"> 13 <button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button> 14 </form> 15 </div> 16 </div> 17 18 <table class="table table-hover table-striped"> 19 <thead> 20 <tr> 21 <th>ID</th> 22 <th>姓名</th> 23 <th>年龄</th> 24 <th>性别</th> 25 <th>诊断</th> 26 <th>入院日期</th> 27 <th>操作</th> 28 </tr> 29 </thead> 30 <tbody> 31 {% for _, patient in patients.iterrows() %} 32 <tr> 33 <td>{{ patient['ID'] }}</td> 34 <td>{{ patient['姓名'] }}</td> 35 <td>{{ patient['年龄'] }}</td> 36 <td>{{ patient['性别'] }}</td> 37 <td>{{ patient['诊断'] }}</td> 38 <td>{{ patient['入院日期'] }}</td> 39 <td> 40 <a href="{{ url_for('patient_detail', id=patient['ID']) }}" class="btn btn-info btn-sm"> 41 <i class="fas fa-eye"></i> 详情 42 </a> 43 <a href="{{ url_for('edit_patient', id=patient['ID']) }}" class="btn btn-primary btn-sm"> 44 <i class="fas fa-edit"></i> 修改 45 </a> 46 <a href="{{ url_for('delete_patient', id=patient['ID']) }}" class="btn btn-danger btn-sm" 47 onclick="return confirm('确定要删除这个病人吗?')"> 48 <i class="fas fa-trash"></i> 删除 49 </a> 50 </td> 51 </tr> 52 {% endfor %} 53 </tbody> 54 </table> 55 56 {% if patients.empty %} 57 <div class="alert alert-info text-center"> 58 暂无病人信息,请先添加病人! 59 </div> 60 {% endif %} 61{% endblock %} 62
4.4 添加病人模板(add.html)

创建add.html模板,显示添加病人表单:
1{% extends "base.html" %} 2 3{% block title %}添加病人 - 医院病人管理系统{% endblock %} 4 5{% block content %} 6 <div class="row justify-content-center"> 7 <!-- 表单卡片(居中显示,限制宽度) --> 8 <div class="col-md-8 col-lg-6"> 9 <div class="card shadow-sm"> 10 <div class="card-header bg-primary text-white"> 11 <h5 class="mb-0"> 12 <i class="fas fa-user-plus mr-2"></i>添加新病人 13 </h5> 14 </div> 15 <div class="card-body"> 16 <!-- 添加病人表单 --> 17 <form action="{{ url_for('add_patient') }}" method="POST"> 18 <!-- 姓名 --> 19 <div class="form-group mb-3"> 20 <label for="name"><i class="fas fa-id-card mr-1 text-primary"></i> 姓名</label> 21 <input type="text" class="form-control" id="name" name="name" 22 placeholder="请输入病人姓名(如:张三)" required> 23 </div> 24 25 <!-- 年龄 --> 26 <div class="form-group mb-3"> 27 <label for="age"><i class="fas fa-calendar-alt mr-1 text-primary"></i> 年龄</label> 28 <input type="number" class="form-control" id="age" name="age" 29 min="0" max="120" placeholder="请输入0-120之间的数字" required> 30 <small class="form-text text-muted">提示:年龄范围建议为0-120岁</small> 31 </div> 32 33 <!-- 性别 --> 34 <div class="form-group mb-3"> 35 <label><i class="fas fa-venus-mars mr-1 text-primary"></i> 性别</label> 36 <div class="form-check"> 37 <input class="form-check-input" type="radio" name="gender" id="male" value="男" checked> 38 <label class="form-check-label" for="male">男</label> 39 </div> 40 <div class="form-check"> 41 <input class="form-check-input" type="radio" name="gender" id="female" value="女"> 42 <label class="form-check-label" for="female">女</label> 43 </div> 44 </div> 45 46 <!-- 病症 --> 47 <div class="form-group mb-3"> 48 <label for="condition"><i class="fas fa-stethoscope mr-1 text-primary"></i> 病症</label> 49 <input type="text" class="form-control" id="condition" name="condition" 50 placeholder="请输入病症(如:感冒、高血压)" required> 51 </div> 52 53 <!-- 住院日期 --> 54 <div class="form-group mb-4"> 55 <label for="admission_date"><i class="fas fa-calendar-check mr-1 text-primary"></i> 住院日期</label> 56 <input type="date" class="form-control" id="admission_date" name="admission_date" required> 57 </div> 58 59 <!-- 提交按钮 --> 60 <div class="text-center"> 61 <button type="submit" class="btn btn-primary px-5"> 62 <i class="fas fa-save mr-1"></i> 保存 63 </button> 64 <a href="{{ url_for('index') }}" class="btn btn-secondary ml-2"> 65 <i class="fas fa-arrow-left mr-1"></i> 返回 66 </a> 67 </div> 68 </form> 69 </div> 70 </div> 71 </div> 72 </div> 73{% endblock %} 74
4.5 详情和编辑模板
创建detail.html模板显示学生详情:

1{% extends "base.html" %} 2 3{% block title %}{{ patient['姓名'] }} - 病人详情{% endblock %} 4 5{% block content %} 6 <div class="row justify-content-center"> 7 <div class="col-md-8 col-lg-6"> 8 <div class="card shadow-sm"> 9 <!-- 卡片头部:病人姓名 --> 10 <div class="card-header bg-info text-white"> 11 <h5 class="mb-0"> 12 <i class="fas fa-user-md mr-2"></i>病人详情 13 </h5> 14 </div> 15 <div class="card-body"> 16 <!-- 病人信息列表 --> 17 <dl class="row"> 18 <dt class="col-sm-3 text-right font-weight-normal">ID编号:</dt> 19 <dd class="col-sm-9">{{ patient['ID'] }}</dd> 20 21 <dt class="col-sm-3 text-right font-weight-normal mt-3">姓名:</dt> 22 <dd class="col-sm-9 mt-3">{{ patient['姓名'] }}</dd> 23 24 <dt class="col-sm-3 text-right font-weight-normal mt-3">年龄:</dt> 25 <dd class="col-sm-9 mt-3">{{ patient['年龄'] }} 岁</dd> 26 27 <dt class="col-sm-3 text-right font-weight-normal mt-3">性别:</dt> 28 <dd class="col-sm-9 mt-3">{{ patient['性别'] }}</dd> 29 30 <dt class="col-sm-3 text-right font-weight-normal mt-3">病症:</dt> 31 <dd class="col-sm-9 mt-3">{{ patient['病症'] }}</dd> 32 33 <dt class="col-sm-3 text-right font-weight-normal mt-3">住院日期:</dt> 34 <dd class="col-sm-9 mt-3">{{ patient['住院日期'] }}</dd> 35 </dl> 36 37 <!-- 操作按钮 --> 38 <div class="mt-5 d-flex justify-content-center"> 39 <a href="{{ url_for('edit_patient', id=patient['ID']) }}" class="btn btn-warning mr-3"> 40 <i class="fas fa-edit mr-1"></i> 编辑信息 41 </a> 42 <a href="{{ url_for('index') }}" class="btn btn-secondary"> 43 <i class="fas fa-arrow-left mr-1"></i> 返回列表 44 </a> 45 </div> 46 </div> 47 </div> 48 </div> 49 </div> 50{% endblock %} 51
创建edit.html模板用于编辑病人信息:

1{% extends "base.html" %} 2 3{% block title %}编辑 {{ patient['姓名'] }} - 医院病人管理系统{% endblock %} 4 5{% block content %} 6 <div class="row justify-content-center"> 7 <div class="col-md-8 col-lg-6"> 8 <div class="card shadow-sm"> 9 <div class="card-header bg-warning text-white"> 10 <h5 class="mb-0"> 11 <i class="fas fa-user-edit mr-2"></i>编辑病人信息 12 </h5> 13 </div> 14 <div class="card-body"> 15 <!-- 编辑表单(预填原有数据) --> 16 <form action="{{ url_for('edit_patient', id=patient['ID']) }}" method="POST"> 17 <!-- 姓名 --> 18 <div class="form-group mb-3"> 19 <label for="name"><i class="fas fa-id-card mr-1 text-warning"></i> 姓名</label> 20 <input type="text" class="form-control" id="name" name="name" 21 value="{{ patient['姓名'] }}" required> 22 </div> 23 24 <!-- 年龄 --> 25 <div class="form-group mb-3"> 26 <label for="age"><i class="fas fa-calendar-alt mr-1 text-warning"></i> 年龄</label> 27 <input type="number" class="form-control" id="age" name="age" 28 min="0" max="120" value="{{ patient['年龄'] }}" required> 29 </div> 30 31 <!-- 性别(默认选中原有值) --> 32 <div class="form-group mb-3"> 33 <label><i class="fas fa-venus-mars mr-1 text-warning"></i> 性别</label> 34 <div class="form-check"> 35 <input class="form-check-input" type="radio" name="gender" id="male" 36 value="男" {% if patient['性别'] == '男' %}checked{% endif %}> 37 <label class="form-check-label" for="male">男</label> 38 </div> 39 <div class="form-check"> 40 <input class="form-check-input" type="radio" name="gender" id="female" 41 value="女" {% if patient['性别'] == '女' %}checked{% endif %}> 42 <label class="form-check-label" for="female">女</label> 43 </div> 44 </div> 45 46 <!-- 病症 --> 47 <div class="form-group mb-3"> 48 <label for="condition"><i class="fas fa-stethoscope mr-1 text-warning"></i> 病症</label> 49 <input type="text" class="form-control" id="condition" name="condition" 50 value="{{ patient['病症'] }}" required> 51 </div> 52 53 <!-- 住院日期 --> 54 <div class="form-group mb-4"> 55 <label for="admission_date"><i class="fas fa-calendar-check mr-1 text-warning"></i> 住院日期</label> 56 <input type="date" class="form-control" id="admission_date" name="admission_date" 57 value="{{ patient['住院日期'] }}" required> 58 </div> 59 60 <!-- 提交按钮 --> 61 <div class="text-center"> 62 <button type="submit" class="btn btn-primary px-5"> 63 <i class="fas fa-save mr-1"></i> 保存 64 </button> 65 <a href="{{ url_for('patient_detail', id=patient['ID']) }}" class="btn btn-secondary ml-2"> 66 <i class="fas fa-arrow-left mr-1"></i> 返回 67 </a> 68 </div> 69 </form> 70 </div> 71 </div> 72 </div> 73 </div> 74{% endblock %} 75
未完待续。。。
《医院病人信息管理系统 Web 版开发实战(一)》 是转载文章,点击查看原文。
