简介这是一份基于Python与Vue.js开发的社区养老管理系统完整源码包采用B/S结构后端使用Python前端使用Vue.js覆盖老人、护工、亲属、病史、房间、活动、用户、日志及系统信息等九大功能模块。适合正在做课程设计、毕业设计或想学习前后端分离全栈实践的开发者可直接参考部署与二次开发。资源包共177个文件约6.02MB主要包括35个Python后端文件、14个Vue前端组件、35个TypeScript逻辑文件、38个SVG图标及JS、HTML、配置与说明文档等目录结构完整方便按功能模块检索学习。目前已有313人学习下载资源信息中提供演示地址与测试账号便于快速了解系统运行效果。整体来看项目结构完整前后端分层清晰覆盖社区养老场景下的核心业务流程适合作为业务系统开发与学习的参考。1. 社区养老管理系统先把 Vue.js 与 Python 的分工想清楚社区养老管理系统听起来是典型的增删改查项目可真正落地时页面数量反而是最小的成本。护理员要接单、打卡、写护理记录管理员要排班、审核、算月费家属要能查账单和健康档案这三条业务链路在 Python 后端和 Vue.js 前端里交错出现逻辑复杂度和踩坑密度都远超预期。这套资源的核心是一个面向社区养老场景的管理系统工程后端以 Python 为主前端基于 Vue.js适合正在做管理系统实践课题、或者准备给小型社区养老机构搭信息化底子的开发者。它能直接解决三类问题基础数据录入与查询、服务工单流转、月度费用结算。读完这篇笔记你能拿到一张可落地的表结构、可以抄的核心接口以及一套部署阶段容易翻车时的排查路径。2. 选型与数据模型为什么用 Flask Vue.js表结构怎么支撑工单和月结这类社区管理项目最常见的技术组合是 Python 后端 Vue.js 前端。后端选 Flask 而不是 FastAPI主要原因是社区养老系统的并发量不高核心是业务状态流转和月末结算Flask 生态简单团队成员接手成本低前端选 Vue.js 则是因为管理后台页面多、组件复用频繁Vue 的单文件组件比传统 jQuery 写法更适合维护。资源里如果要把登录、权限、工单、账单串起来一开始就得把模块边界和表结构定清楚否则后面改一版崩一版。2.1 模块边界与三类角色社区养老系统至少要有三类使用者管理员、护理员、家属。管理员维护老人档案、服务项目、员工信息生成每月账单护理员查看排班、接单、填写护理记录家属只能查看自己绑定的老人信息和账单。前端可以做成一个管理后台也可以给家属单独做 H5 页面但接口层一定要按角色控制不然家属拿到管理员接口就能看到全站数据。模块划分建议按业务域拆老人档案、员工管理、服务项目、排班计划、工单管理、护理记录、费用账单、系统登录。不要把费用计算逻辑塞进工单接口里工单只负责状态流转费用由独立的月结任务去汇总。这样拆的好处是后期要加一个“退住结算”或者“临时加餐服务”不需要改动工单状态机只增加账单计算规则即可。权限矩阵可以简单到用角色字符串控制资源里常见做法是后端在接口上做校验前端只控制菜单和按钮显隐。不要完全信任前端的权限判断Vue 里的路由守卫只是体验优化真正的安全边界必须放在 Python 接口上。2.2 核心表设计与状态字段我一般会把表控制在八张左右用户表、老人表、家属关联表、服务项目表、排班表、工单表、护理记录表、账单表。表太多会拖累开发速度表太少后期统计报表又很难写。from datetime import datetime from sqlalchemy import Column, Integer, String, Date, DateTime, Numeric, Text, ForeignKey from sqlalchemy.orm import declarative_base, relationship Base declarative_base() class User(Base): __tablename__ user id Column(Integer, primary_keyTrue, autoincrementTrue) username Column(String(50), uniqueTrue, nullableFalse, indexTrue) password_hash Column(String(128), nullableFalse) role Column(String(20), nullableFalse) # admin / staff / family real_name Column(String(50)) phone Column(String(20)) status Column(Integer, default1) # 1 启用 0 停用 created_at Column(DateTime, defaultdatetime.now) class Elder(Base): __tablename__ elder id Column(Integer, primary_keyTrue) name Column(String(50), nullableFalse) id_card Column(String(18), uniqueTrue) gender Column(String(2)) birthday Column(Date) room_no Column(String(20)) care_level Column(String(20)) # self-care / semi-care / full-care chronic_disease Column(String(255)) medication Column(String(255)) emergency_contact Column(String(50)) status Column(Integer, default1) # 1 在住 0 退住用户表不建议把角色拆成三张表社区项目规模小一个role字段就够了真要做复杂的 RBAC 反而增加维护成本。老人表里的care_level不是给人看的备注它要参与费用系数计算所以取值必须固定不要允许手工输入“半自理”和“半护”这种同义不同值的写法。工单表是整条业务链的中枢建议这样设计核心字段字段说明约束elder_id老人 ID必填关联 elder 表item_id服务项目 ID必填决定单价assignee_id护理员 ID必填关联 user 表status工单状态pending / doing / done / cancelledscheduled_time计划服务时间可空用于排班视图actual_time实际完成时间完成时写入工单状态不要用数字 0/1/2直接用字符串更容易在前后端对齐。前后端联调时状态值写错是最常见的问题比如前端传了已完成后端存的是done查询接口永远返回空列表这种问题排查起来比代码逻辑错误更费时间。账单表按月生成核心字段包括老人 ID、账期年 / 月、基础服务费、项目费用合计、优惠金额、应收金额、账单状态。费用计算先按服务项目单价乘次数汇总再叠加护理等级系数。这里有一个容易忽略的点基础服务费按天计算月中入住和退住都要按天折算不能一刀收取整月费用否则家属端很快就会有投诉工单进系统。3. Python 业务链路从 JWT 鉴权到费用结算的三个核心接口业务链路的骨架是登录鉴权、工单流转、月结账单。这三个接口写顺了其余的老人档案 CRUD、排班管理都只是体力活。下面按实际开发顺序拆解。3.1 登录与 JWT 鉴权社区养老系统不需要引入 OAuth 那套重方案JWT 足够用。用户登录成功后返回 token前端存在 localStorage每次请求在 Authorization 头里带上。后端写一个装饰器统一做登录态和角色校验。import jwt from functools import wraps from flask import request, jsonify from config import SECRET_KEY def login_required(rolesNone): def decorator(fn): wraps(fn) def wrapper(*args, **kwargs): auth request.headers.get(Authorization, ) token auth.replace(Bearer , ) try: payload jwt.decode(token, SECRET_KEY, algorithms[HS256]) except jwt.PyJWTError: return jsonify(code401, msg登录已过期或身份无效), 401 if roles and payload.get(role) not in roles: return jsonify(code403, msg当前角色无权操作), 403 request.current_user payload return fn(*args, **kwargs) return wrapper return decorator逻辑说明先从请求头取出 Authorization再把 Bearer 前缀去掉解码失败统一返回 401。解码成功后再做角色判断roles传[admin, staff]就表示只有管理员和护理员能访问。参数说明SECRET_KEY放在独立配置文件中不要写在业务代码里algorithms必须显式指定避免依赖环境默认值。前端退出登录时后端不需要主动销毁 token直接让 token 过期就行JWT 本身就是无状态方案强行做服务端黑名单反而增加额外存储。3.2 创建工单与完成闭环护理员端创建一个服务工单必须校验老人和服务项目是否都传了。状态初始为pending表示等待执行。app.route(/api/work_order, methods[POST]) login_required(roles[admin, staff]) def create_work_order(): data request.get_json(forceTrue) elder_id data.get(elder_id) item_id data.get(service_item_id) scheduled_time data.get(scheduled_time) if not elder_id or not item_id: return jsonify(code400, msg老人和服务项目不能为空), 400 work_order WorkOrder( elder_idelder_id, item_iditem_id, assignee_idrequest.current_user[id], statuspending, scheduled_timescheduled_time ) db.session.add(work_order) try: db.session.commit() except Exception: db.session.rollback() return jsonify(code500, msg保存失败请稍后重试), 500 return jsonify(code0, msgok, data{work_order_id: work_order.id})逻辑说明工单创建人不从前端传而是从 token 里取避免护理员伪造别人 ID。forceTrue用于兼容部分客户端没有正确设置 Content-Type 的情况但如果参数不是 JSON 格式会直接在request.get_json处抛异常这里用 try 包住整个提交过程属于最基本的容错。完成工单时要做两件事更新工单状态、写入一条护理记录。护理记录里冗余了服务项目 ID 和护理员 ID这样后续做统计报表时不用反复 join 多张表。app.route(/api/work_order/int:oid/finish, methods[POST]) login_required(roles[staff, admin]) def finish_work_order(oid): order WorkOrder.query.get(oid) if not order or order.status ! pending: return jsonify(code400, msg工单不存在或状态不允许), 400 record CareRecord( elder_idorder.elder_id, work_order_idorder.id, staff_idrequest.current_user[id], service_item_idorder.item_id, noterequest.get_json(silentTrue) or {} ) order.status done db.session.add(record) db.session.commit() return jsonify(code0, msgok, data{record_id: record.id})这个地方最容易踩坑的是并发护理员手速快连点了两次“完成”两个请求同时读到status pending于是插入两条护理记录。后面在避坑章节会专门给出条件更新的写法。接口返回record_id而不是只返回 ok就是为了让前端可以跳转到详情页或者打印本次护理凭据。3.3 月度费用结算月结逻辑适合用定时任务触发管理员也可以在后台手动触发某个月份的账单生成。核心步骤是先查该月所有已完成的工单按老人和服务项目分组统计次数再叠加基础服务费。app.route(/api/billing/generate, methods[POST]) login_required(roles[admin]) def generate_bill(): data request.get_json(silentTrue) or {} year data.get(year) month data.get(month) if not year or not month: return jsonify(code400, msg请指定账期), 400 elders Elder.query.filter_by(status1).all() for elder in elders: items db.session.query( CareRecord.service_item_id, func.count(CareRecord.id) ).filter( CareRecord.elder_id elder.id, func.date_format(CareRecord.actual_time, %Y-%m) f{year}-{month:02d} ).group_by(CareRecord.service_item_id).all() # 计算项目费用叠加护理等级系数写入 bill 表 ... return jsonify(code0, msg账单生成完成)逻辑说明只对status 1的在住老人生成账单退住老人的退住结算走单独接口。项目费用按工单完成时间归属账期而不是按创建时间因为跨月订单很常见6 月底下单、7 月初完成的服务费用应该计入 7 月。参数说明date_format是 MySQL 的函数如果换成 SQLite 本地调试函数名和写法会不一样。资源里如果同时支持 SQLite 和 MySQL建议把这段逻辑改为先取出当月记录再在 Python 内存里做统计避免迁移数据库时踩函数兼容的坑。月结任务跑完后把生成的账单数量写入日志管理员端每次手动触发都要显示“生成 126 张账单”而不是笼统提示“生成成功”。4. Vue.js 前端装配路由守卫、请求封装和权限菜单一次打通Vue.js 前端要做的核心事情有三件封装 axios 请求、守住路由权限、根据角色动态渲染菜单。下面按顺序拆。4.1 请求封装与统一响应体后端接口返回格式建议统一为{ code, msg, data }的结构。code 0表示成功其他值表示业务失败。前端在响应拦截器里统一处理业务代码不需要每个页面都写错误弹窗。import axios from axios import { ElMessage } from element-plus import router from /router const service axios.create({ baseURL: /api, timeout: 8000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( res { const data res.data if (data.code 0) return data.data ElMessage.error(data.msg || 请求失败) return Promise.reject(new Error(data.msg)) }, err { if (err.response err.response.status 401) { localStorage.removeItem(token) router.replace(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(err) } ) export default service逻辑说明请求拦截器统一携带 token响应拦截器负责剥离data层页面里const list await getElderList()拿到的直接就是列表数组不用再写res.data.data这种嵌套取值。参数说明timeout设 8000 毫秒社区养老系统的文件导出接口如果超过 8 秒需要单独对上传导出类接口做更长的超时配置不能一个 timeout 走天下。401跳转登录页只做一次避免多个并发请求同时 401 导致路由重复跳转可以在拦截器里加一个isRedirecting标志位。4.2 路由守卫与角色权限Vue Router 在每次跳转前判断登录态和角色。配置路由时用meta.roles声明哪些角色能访问。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.public) { next() return } if (!token) { next(/login) return } const role localStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) { next(/403) return } next() })逻辑说明meta.public标记登录页、404 页面这类不需要登录的页面meta.roles数组用于控制页面级权限比如账单生成页只放admin。数据量大的系统不建议在路由表里预写全部菜单而是登录后从后端拉取可访问菜单再动态 addRoute但这套资源如果只有十几个页面预写路由加守卫判断就够用了。角色判断还有一个细节前端路由守卫只能控制页面能不能进入不能控制页面里的按钮。删除老人、作废账单这类危险操作按钮显示与否还要用指令或 v-if 判断角色。const canDelete computed(() { return localStorage.getItem(role) admin })逻辑说明v-if 控制按钮只是防止误操作真正拦截还是靠后端接口角色校验。很多管理系统翻车都是因为只做了前端隐藏按钮接口里没做鉴权懂行的人直接 curl 调用删除接口就能把数据清空。4.3 动态菜单与权限指令管理后台用 Element Plus 的el-menu渲染侧边栏菜单配置可以放在前端静态文件里每个菜单项声明 roles。const menuItems [ { path: /dashboard, title: 工作台, roles: [admin, staff, family] }, { path: /elder, title: 老人档案, roles: [admin, staff] }, { path: /work-order, title: 工单管理, roles: [admin, staff] }, { path: /billing, title: 账单管理, roles: [admin] }, { path: /family-bill, title: 我的账单, roles: [family] } ]模板里渲染时过滤roles不包含当前角色的菜单项。这种静态过滤方式对页面少的小系统足够不需要额外请求菜单接口。菜单和路由双份配置可能会让新手困惑但只要记住路由表决定“能不能访问”菜单数组决定“看得到什么入口”二者用path字段关联即可。前端还有一个常见坑家属登录后也能访问/elder页面虽然在接口层会被后端拦截但页面会闪烁一下再跳 403体验很差。所以前端一定要把菜单过滤和路由守卫都配上让家属端从入口处就看不到他用不了的模块。5. 部署与避坑跨域、乱码、并发、时区、连接池五个高频问题这套系统在本地跑通不算难真正让人头疼的是部署阶段。下面这五个问题我几乎在每个类似项目里都见过按排查顺序列出来。5.1 前端 history 路由刷新后 404现象Vue 项目打包部署到 Nginx 后登录页能打开但点击进入“工单管理”再刷新页面直接报 404路径没问题刷新就丢。原因Vue Router 使用的是 history 模式浏览器地址栏里的路径是/work-orderNginx 找不到这个真实文件默认会返回 404。本地开发时有 Node 服务兜底部署到 Nginx 后没有配置 fallback。解决在 Nginx 的location配置中把所有非文件请求都重写到index.htmllocation / { try_files $uri $uri/ /index.html; }这段配置的意思是先找真实文件和目录找不到就返回index.html由 Vue Router 接管路由解析。配置完要执行nginx -t检查语法并 reload。如果后端接口也走同一个域名要注意把/api单独代理出去不要被index.html吞掉。5.2 MySQL 中文乱码或建表报错现象前端录入老人姓名“张阿姨”保存后变成“寮犻樋濮”。报错信息里出现Incorrect string value。原因MySQL 连接串或建表语句没有指定utf8mb4。很多环境默认字符集是latin1或utf8mb3存生僻字足够用但存不了真正的中文场景字符集。解决建库指定字符集同时连接串显式声明CREATE DATABASE community_care DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;SQLALCHEMY_DATABASE_URI ( mysqlpymysql://user:password127.0.0.1:3306/community_care ?charsetutf8mb4 )从 Flask 到 SQLAlchemy 再到 PyMySQL每一层能指定编码的地方都指定上。老项目如果是直接执行的 SQL 文件导入检查表结构里的CHARSET字段经常有建表和连接串不一致的情况。数据已经乱掉的话别想着直接改回先导出数据、重建表、再按utf8mb4导入才是后悔药有效吃法。5.3 并发完成工单导致护理记录重复现象护理员在“完成”按钮上被前端 loading 卡住连续点了两次后台上出现两条完全相同的护理记录。原因后端接口逻辑是“先查工单状态再更新为 done”两个请求同时进来时两个线程都读到status pending于是都走完更新流程。查询和更新之间没有原子保护。解决把“状态必须为 pending”这个条件放进 UPDATE 语句里用受影响行数判断是否执行成功。updated WorkOrder.query.filter_by( idoid, statuspending ).update({status: done}) if not updated: db.session.rollback() return jsonify(code400, msg工单状态已变化请勿重复操作), 400 db.session.commit()逻辑说明条件更新自带乐观锁效果只有第一个请求能把 pending 改成 done第二个请求影响行数为 0直接返回业务失败。使用这种方式后不需要在工单表加 version 字段适合这种轻量场景。数据库更新是排他锁操作比应用层try_lock更可靠。5.4 日期错一天或账单归属月不对现象家属在 8 月 1 日早上看到上个月的账单发现有几条护理记录日期变成了 7 月 31 日而实际服务时间是 8 月 1 日凌晨。原因Flask 默认使用 UTC 时间Python 的datetime.now()返回的是本地时间但 MySQL 的DATETIME类型不保存时区信息。如果部分接口用datetime.now()部分用datetime.utcnow()就会产生 8 小时位移。解决项目里统一使用本地时区不要在同一个项目里混用utcnow和now。from datetime import datetime from zoneinfo import ZoneInfo def now_local(): return datetime.now(ZoneInfo(Asia/Shanghai))逻辑说明所有模型字段的 default 都调用now_local()避免逐个手工传时间。数据库连接配置里也可以设置init_commandSET time_zone 08:00双保险。如果你打算部署到海外服务器再考虑统一 UTC 存储、前端按浏览器时区格式化但社区养老系统服务对象就在本地直接固定上海时区更贴合业务。5.5 跑一段时间后数据库连接池耗尽现象系统运行几周后接口偶尔卡住日志报OperationalError: (MySQLdb) (1040, Too many connections)重启后又恢复正常过几天再犯。原因Flask 调试模式下 SQLAlchemy 每次请求都创建新连接或者部署服务的进程重启不够MySQL 的wait_timeout把空闲连接断掉但连接池还在用旧连接。多 worker 部署时连接数会被 worker 数放大。解决在 SQLAlchemy 引擎参数里配置连接池参数engine create_engine( SQLALCHEMY_DATABASE_URI, pool_size10, max_overflow20, pool_recycle3600, pool_pre_pingTrue )逻辑说明pool_pre_ping是最实用的一项每次从池里取连接前先发一个轻量 ping连接已经被 MySQL 断开时自动丢弃并新建。pool_recycle设置为 3600 秒比 MySQL 的wait_timeout默认 8 小时短很多能主动回收闲置连接。本地调试时如果发现连接数异常先把 Flask 调试模式关掉再检查是否每次请求都新建了 Engine。6. 从能跑到能交付验收清单和三个提效技巧这套系统在开发环境跑通只是第一步真正交付给社区管理员时要按业务主流程验收一遍。我每次都会列一张清单管理员登录工作台、新增老人、为老人绑定家属账号、创建服务项目、给护理员排班、护理员接单并完成、家属登录查看账单、管理员生成月结账单并标记已收。走完这一遍系统才算闭环。三个提效习惯很值得养成。第一个是统一响应体后端所有接口都返回{ code, msg, data }前端拦截器里统一处理新增页面时复制模板就能开工不需要每个页面写重复的错误弹窗逻辑。第二个是写一键初始化脚本把管理员账号、默认服务项目、三名测试老人、两个护理员一次性写入数据库避免每次清库后手工在页面上造数。脚本用独立文件管理不影响正常业务代码排查问题前先跑一遍重置数据。# seed_demo.py 示例 def init_demo_data(): db.drop_all() db.create_all() admin User(usernameadmin, password_hashgen_hash(admin123), roleadmin) staff User(usernamenurse01, password_hashgen_hash(nurse123), rolestaff) elder Elder(name张阿姨, room_no301, care_levelsemi-care) db.session.add_all([admin, staff, elder]) db.session.commit()第三个技巧是联调阶段把前端代理配好不要直接用完整域名访问后端接口避免每次改接口地址都要重新打包。从那以后我每次接手这类管理系统都强制自己先跑主流程验收清单再补异常分支。顺序一颠倒后面全是返工。希望这篇笔记帮你在社区养老管理系统上少走几步弯路。本文还有配套的精品资源点击获取