基于Python FastAPI与Vue3的宿舍后勤管理系统开发实战
基于 Python Vue3 的员工宿舍后勤管理系统从业务建模到部署落地的完整实践后勤管理系统是很多公司容易忽略、但真实痛点集中的领域。员工宿舍管理表面上看是“记录谁住哪间房”但实际做起来涉及入住退宿、费用结算、床位资源统计、报修闭环、权限区分等一堆琐碎环节。本文基于 PythonFastAPI Vue3 的完整前后端分离方案梳理整个系统从零搭建的过程为什么选这套技术栈、数据表怎么设计、前端页面如何组织、联调期有哪些高频坑、最后怎么部署上线以及未来可以扩展的方向。适合正在做类似小型管理系统的开发者、做毕业设计的同学以及公司内部需要快速搭建后勤工具的技术团队参考。1. 为什么“宿舍管理”听着简单做起来全是细节1.1 业务场景里最容易漏掉的需求我见过不少团队把宿舍管理系统当成“学生作业”来做结果一上线就被宿管阿姨和行政同事连续反馈打蒙。这个系统真正的复杂度不在技术而在业务。先说入住登记。一个员工入职分配宿舍看起来就是“填个表单选个房间”的事。但实际问题包括这人是住单人间还是双人间这栋楼和这层的朝向有没有特殊要求同一部门的人要不要尽量安排在一起有没有禁烟楼层还有更常见的——某个房间已经住了三个人但系统只记录“是否住满”没用记录“还剩几个空床”那协调入住的时候就只能靠翻纸质台账。再说退宿和调宿。员工离职要办退宿宿管检查房间设施、收回钥匙、结算水电费这些线下流程如果系统不跟踪就很容易出现“人走了系统里还显示占着床位”。调宿更麻烦员工从 A 房间换到 B 房间中间有没有费用差异原房间剩余的水电费怎么处理这些细节不提前建模后面写代码会很痛苦。还有费用计算。宿舍水电费通常按房间结算不是按人头。有的公司每月固定补贴一部分超出部分员工自理。补贴额度、阶梯电价、公摊损耗每个公司规则都不一样。如果系统里只存一个“金额”字段那等于没有设计——你需要把计算规则抽象出来而不是把结果写死。权限也经常被忽略。普通员工应该只能看到自己的入住信息宿管员能管理房间和入住行政主管看统计报表财务看账单但不需要碰房间管理。这种多角色控制如果一开始图省事不做后面补起来成本极高。最后是维修报修。宿舍里的空调、热水器、门锁坏的概率比想象中高。报修不是简单“记录一条工单”而是要有状态流转待接单、处理中、已完成、已回访。如果系统不做状态机工单就会变成一条只读通知。1.2 为什么选 Python Vue3而不是传统模板渲染这个项目我用的是“前后端分离 后端接口 前端单页应用”的方案。后端用 Python 的 FastAPI 框架前端用 Vue3 Element Plus 组件库。选 Python 的原因很直接它是很多内部系统团队最熟悉的技术栈。用 FastAPI 而不是 Flask 或 Django是因为这类系统接口数量大概在 30~50 个左右需要清晰的参数校验和自动生成的接口文档。FastAPI 自带 OpenAPI 文档后端写完接口前端可以对着文档联调省去大量口头沟通。Vue3 的选择也很好理解。对于这种中后台管理系统Vue3 的 Composition API 配合 Element Plus组件复用和页面组织效率很高。相比 Vue2Vue3 的响应式系统重写后在处理表格、弹窗、表单这类高频交互时性能更好而且 TypeScript 支持更友好。这套系统要处理的数据结构不算复杂但页面之间的状态关联不少——比如选房间时要实时看到剩余床位和入住员工列表这种场景下组件化开发比传统多页模板自然得多。1.3 系统模块怎么拆我把整个系统拆成 5 个核心模块每个模块对应一张主表加若干关联表模块解决的核心问题主要数据对象房间管理宿舍资源可视化空闲/入住/维修状态一目了然房间、床位、楼栋入住管理入住、退宿、调宿全流程历史记录可追溯入住记录、员工信息水电账单按月生成房间账单记录缴费状态账单、缴费记录报修管理工单流程闭环状态可跟踪报修单、处理记录系统管理用户账号、角色权限、基础数据用户、角色、日志另外有一个 dashboard 首页用于展示入住率、月度水电费趋势、待处理工单数等关键指标方便管理员一眼掌握宿舍运行状态。2. 后端设计FastAPI 怎么把“后勤账本”做得清楚2.1 数据模型设计——先想清楚宿舍业务再建表我强烈建议先画数据关系再写代码。宿舍系统的核心是“房间”和“入住记录”一切费用和工单都挂在这两条主线下面。房间表rooms字段大致如下class Room(Base): __tablename__ rooms id Column(Integer, primary_keyTrue) room_no Column(String(20), uniqueTrue, nullableFalse) # 房间号如 A-3-501 building Column(String(20), nullableFalse) # 楼栋 floor Column(Integer, nullableFalse) # 楼层 room_type Column(String(10), nullableFalse) # single / double / four bed_count Column(Integer, nullableFalse) # 床位数 bed_used Column(Integer, default0) # 已用床位数 status Column(String(10), defaultavailable) # available / occupied / repair remark Column(String(255))注意不要直接通过“查入住记录里有多少未退宿的人”来实时推导房间实住人数那样每次刷新页面都要做聚合查询性能差且逻辑分散。我这里在房间表里维护一个bed_used冗余字段办理入住时 1、退宿时 -1然后用数据库事务保证准确。这是小系统里非常实用的做法。员工表employees和入住记录表check_insclass CheckIn(Base): __tablename__ check_ins id Column(Integer, primary_keyTrue) employee_id Column(Integer, ForeignKey(employees.id)) room_id Column(Integer, ForeignKey(rooms.id)) bed_no Column(Integer) # 入住的床位编号例如 2 check_in_date Column(Date) check_out_date Column(Date, nullableTrue) status Column(String(10), defaultactive) # active / closed入住的“快照”是关键。员工当前住哪、历史上住过哪、每个时间段对应的房间和床位是多少都通过这条记录查出来。退宿不删除记录只把status改成closed并填上check_out_date。这样后续想追溯“上季度住宿情况”时一条 SQL 就能查出来。水电账单表需要冗余房间号和期间避免之后房间换号导致账单归属错乱class UtilityBill(Base): __tablename__ utility_bills id Column(Integer, primary_keyTrue) room_id Column(Integer, ForeignKey(rooms.id)) period Column(String(7), nullableFalse) # 月份如 2025-06 water_amount Column(Numeric(10, 2), default0) electricity_amount Column(Numeric(10, 2), default0) water_fee Column(Numeric(10, 2), default0) electricity_fee Column(Numeric(10, 2), default0) total_fee Column(Numeric(10, 2), default0) status Column(String(10), defaultunpaid) # unpaid / paid这里金额字段统一用Numeric(10,2)。用 Python 的 float 存金额会在累计和比较时出现精度问题这种坑我已经踩过不止一次。Numeric转出来的 Decimal 对象在 JSON 序列化时需要处理FastAPI 默认会输出成字符串后面联调期我会讲到怎么解决。报修单表repair_orders核心就是一个状态机class RepairOrder(Base): __tablename__ repair_orders id Column(Integer, primary_keyTrue) room_id Column(Integer, ForeignKey(rooms.id)) report_by Column(Integer, ForeignKey(employees.id)) category Column(String(20)) # 空调 / 水电 / 门窗 / 其他 description Column(Text) status Column(String(10), defaultpending) # pending / processing / done / closed handler Column(String(50)) create_time Column(DateTime, defaultdatetime.now) finish_time Column(DateTime, nullableTrue)状态流转我用简单的 if 判断来控制没有上 Python 的状态机库——因为状态只有 4 个硬上状态机库反而增加阅读难度。2.2 接口风格与统一返回格式所有接口约定统一的 JSON 结构这是我不管做什么后端项目都坚持的规范{ code: 0, message: success, data: {} }业务成功时code0参数错误返回code40001鉴权失败返回code40100服务器内部异常返回code50000。这样前端 axios 拦截器里只需要判断code一个字段不用每次分别处理HTTP 200 但业务失败和HTTP 500的混乱情况。接口命名走 RESTful 风格。房间相关GET /api/rooms 分页查询筛选 building / status POST /api/rooms 新增房间 PUT /api/rooms/{id} 编辑房间 DELETE /api/rooms/{id} 删除房间入住相关POST /api/check-ins 办理入住传入员工ID、房间ID、床位号 POST /api/check-ins/{id}/checkout 办理退宿 GET /api/check-ins/history 入住记录查询报表相关GET /api/dashboard/summary 入住率总览 GET /api/dashboard/utility-trend 近6个月水电费分页参数统一用page和page_size返回结构里带上total{ code: 0, data: { items: [], total: 137, page: 1, page_size: 20 } }2.3 鉴权与权限管控这个系统有三种角色普通员工、宿管员、管理员。我用 JWT 做登录态密码用bcrypt哈希存储。登录流程很简单用户提交账号密码后端校验通过后用 PyJWT 签发一个有效期 24 小时的 tokentoken 里只放user_id和role。前端把 token 存在 localStorage每次请求通过 axios 拦截器放到Authorization: Bearer token头里。权限管控我用了 FastAPI 的依赖注入机制写一个通用的require_role依赖async def require_role(role: str): def verify( authorization: str Header(...), db: Session Depends(get_db) ): token authorization.replace(Bearer , ) payload jwt.decode(token, SECRET_KEY, algorithms[HS256]) user db.query(User).get(payload[user_id]) if user.role ! role: raise HTTPException(status_code403, detail无权限) return user return verify然后在需要权限的接口上直接套app.post(/api/rooms, dependencies[Depends(require_role(admin))]) def create_room(...): ...普通员工登录后只能调用自身相关的查询接口宿管员能处理入住和工单admin 才能管理房间和账号。这个模型简单但足够覆盖大部分内部系统的权限需求。3. Vue3 前端一个后台管理的页面怎么组织才不乱3.1 项目骨架与依赖版本前端我用的工程化方案是 Vite Vue3 TypeScript Element Plus Pinia Vue Router。Vite 作为开发服务器和打包工具启动速度和 HMR 体验比 Webpack 时代好太多。创建项目可以直接用 Vite 官方脚手架pnpm create vite my-dormitory-admin --template vue-ts cd my-dormitory-admin pnpm add vue-router4 pinia axios element-plus这里有个细节Element Plus 的完整引入和按需引入。开发阶段图方便可以全量引入但打包体积会大不少。我建议用 unplugin-auto-import 和 unplugin-vue-components 这两个插件做按需引入只打包用到的组件。TypeScript 配置方面我在 tsconfig.json 里开了strict: true。刚开始确实会有大量类型报错但项目跑起来后改一处牵扯到其他页面时的收益非常明显。热搜词里有人问“若依 vue3 ts 报错”大概率就是因为模板项目开了 strict 而自己没有空值保护习惯后面我会在联调章节提到几个高频类型问题。3.2 路由、登录态与页面权限设计路由结构按页面模块划分/login 登录页 / 主布局侧边栏 顶栏 内容区 /dashboard 首页看板 /rooms 房间管理 /check-ins 入住管理 /bills 水电账单 /repairs 报修管理 /system/users 用户管理路由守卫是登录态控制的关键。我在router.beforeEach里做两步判断router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else if (!token) { next(/login) } else { next() } })这只是最基础的登录拦截。权限按钮级的控制我写了一个自定义指令v-permission传入需要的角色数组如果当前用户角色不在里面就自动把这个元素移出 DOM。这样宿管员账号看房间页面时只会看到“办理入住”按钮而看不到“新增房间”按钮。Pinia 里维护两个核心 storeuserStore存 token、用户信息、角色和权限列表roomStore存当前筛选条件下的房间列表和加载状态。不要在多个组件里各自请求同一份房间数据否则切换页面时会出现画面跳动。3.3 核心页面拆解房间可视化、费用账单、报修流程房间管理页是这个系统最核心的界面我用的是“左侧房间卡片网格 右侧抽屉详情”的交互形式。每个房间渲染成一张卡片卡片颜色按状态区分绿色表示可用、橙色表示部分入住、红色表示已住满、灰色表示维修中。点开卡片右侧抽屉显示房间详细信息和入住员工列表抽屉底部放“办理入住”“办理退宿”“发起报修”三个操作按钮。卡片组件里有一个细节房间状态的判定不能只靠room_status字段。比如一个双人间住了 1 个人状态可能是available但又有bed_used0前端要根据bed_used和bed_count的对比关系动态算出“部分入住”的展示状态。也就是说数据库存的status字段只是业务数据展示层要基于业务规则做二次计算。水电账单页我用了 Element Plus 的 el-table 加月份筛选。表格列展示房间号、上月表底、本月表底、用电量、电费、水费、合计、缴费状态。缴费状态用 el-tag 展示颜色区分。这里有个小交互点财务人员需要批量确认缴费所以表格第一列是全选 checkbox顶部一个“批量标记已缴”按钮调后端接口批量更新账单状态。报修管理页是典型的“列表 状态流转”页面。列表按状态筛选工单详情用对话框展示。操作按钮根据状态动态渲染待处理状态显示“接单”接单后显示“完成维修”完成后显示“关闭工单”。每次操作后回调刷新接口避免页面出现脏状态。4. 前后端联调期我踩过的几个坑以及解法4.1 跨域问题开发阶段前端跑在localhost:5173后端跑在localhost:8000天然跨域。后端的 FastAPI 需要配 CORSMiddlewareapp.add_middleware( CORSMiddleware, allow_origins[http://localhost:5173], allow_credentialsTrue, allow_methods[*], allow_headers[*], )注意allow_origins用列表明确写好来源不要图省事用[*]。配合allow_credentialsTrue时[*]会导致浏览器直接拦截必须写明具体源。生产环境我不用 CORS 放行而是用 Nginx 反向代理把/api前缀转发到后端服务这样前后端在浏览器看来同源彻底绕开跨域问题。开发期配 CORS生产期用代理这是我固定的处理策略。4.2 日期与金额字段的类型陷阱FastAPI 返回的 datetime 字段默认序列化成 ISO 格式比如2025-06-01T10:30:00Z。前端如果用原生 Date 对象去接收直接显示出来会是一行莫名其妙的英文日期。我的做法是后端在 Pydantic 模型里统一指定格式class CheckInOut(BaseModel): check_in_date: str ...然后从 ORM 对象转 dict 时展开成date.strftime(%Y-%m-%d)。这样前端拿到的是2025-06-01直接用字符串渲染就行根本不用 dayjs 解析。金额字段的坑更隐蔽。FastAPI 序列化Decimal(10, 2)时默认会输出成字符串23.50而不是数字23.5。前端表格展示没问题但如果你要做金额汇总计算字符串参与运算就会出错。我推荐前端拿金额字段后统一走一个formatMoney工具函数入参声明为string | number内部用 Number 转换后再运算、再格式化输出。别指望后端给你数字类型因为 Decimal 转 float 又有精度损失两害相权字符串 前端转换反而最稳定。4.3 状态枚举不一致这是联调期最容易出现 bug 的地方。后端房间状态的取值是available / occupied / repair前端组件里写的却是可用 / 已满 / 维修中结果就是前端传参时传的是中文后端直接校验报错。或者后端改了枚举值前端没同步更新界面上所有房间都变成灰色不可用。这些问题靠沟通效率太低。我的解决方案是后端提供一份字典接口GET /api/dicts返回所有枚举取值及其说明前端在全局状态里拉取一次生成映射表。页面渲染时通过映射表把英文取值翻译成中文标签提交表单时反向翻译成英文。这套机制建好后前后端枚举再也不用手动同步改后端枚举值前端刷新页面自动适配。此外前后端要约定枚举值只允许“后端定义的所有合法取值”。前端输入枚举值前先向后端字典接口校验避免出现“改了一处类型之后其他全是 undefined”的连锁问题。4.4 axios 拦截器与 token 过期刷新axios 拦截器是前后端联调的最后一个大坑。我的拦截器分两层请求拦截器自动给请求头加 token响应拦截器统一处理业务码、HTTP 错误和 token 过期。service.interceptors.response.use( (response) { const res response.data if (res.code ! 0) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, (error) { if (error.response?.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.response?.data?.detail || 网络异常) return Promise.reject(error) } )token 过期刷新的逻辑我没做太复杂因为内部系统 token 有效期设成 24 小时上班时间基本不会过期。真过期了就重新登录成本和风险都可控。如果你要在 token 过期时静默刷新需要额外维护 refresh_token 的接口和并发请求队列复杂度会上一个台阶对于这种后勤系统我认为不值得。5. 从开发机到服务器部署上线那点事5.1 服务器环境与数据库选型我部署用的是一台 2C4G 的云服务器跑 CentOS 系统。小系统的后端完全够用。数据库这里有一个现实而微妙的取舍数据量不大时用 SQLite 就够了省去一台数据库服务器的维护成本。但 SQLite 在并发写入场景下表现确实较弱如果公司人多、同时办理入住退宿和录入水电费可能会出现database is locked的错误。我的建议系统上线初期月活不超过 200 人用 SQLite 加 WAL 模式完全能撑住。等到真的数据量大了后端代码里把 SQLAlchemy 的连接串从 SQLite 换成 PostgreSQL 即可模型代码不用改。切换成本存在于部署环境而不是代码层。5.2 后端进程托管后端用 uvicorn 启动只适合开发调试生产环境需要 gunicorn 配合 uvicorn worker。写一个 systemd 服务文件[Unit] DescriptionDormitory API Server Afternetwork.target [Service] Userwww WorkingDirectory/opt/dormitory-api EnvironmentPATH/opt/dormitory-api/venv/bin ExecStart/opt/dormitory-api/venv/bin/gunicorn main:app \ -k uvicorn.workers.UvicornWorker \ -w 2 \ -b 127.0.0.1:8000 Restartalways [Install] WantedBymulti-user.target一个容易忽略的点gunicorn 的 worker 数量不要盲目设成CPU 核心数 * 2 1。2 核服务器用 2 个 worker 就够每个 uvicorn worker 是事件循环模型本身就能处理大量并发worker 数设太多反而增加内存占用。内存只有 4G 的服务器开 4 个 worker 很有可能 OOM。启动服务后用curl http://127.0.0.1:8000/api/dashboard/summary测一次能通再进入下一步。5.3 前端构建与 Nginx 反向代理前端打包pnpm build产物在dist/目录上传到服务器的/opt/dormitory-web然后配置 Nginxserver { listen 80; server_name your-domain.com; root /opt/dormitory-web; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } }try_files那行很重要——Vue Router 用的 history 模式前端路由跳转刷新页面时Nginx 要回退到 index.html否则刷新二级页面会 404。如果你不想处理这个问题可以直接改用 hash 模式但 URL 多一个#观感差一些。数据库备份我用最简单的方案每天凌晨用 cron 执行一次 SQLite 文件复制到备份目录保留 7 天。PostgreSQL 的话可以用pg_dump。小系统的备份策略不需要复杂只要能防住误删除和服务器故障就够。6. 这套系统还能怎么长几个我推荐的扩展方向6.1 与企业微信/钉钉通知打通报修工单状态变化目前要靠用户刷新页面才能看到。可以对接企业微信群机器人或者钉钉自定义机器人通过 webhook 发消息提交工单时通知宿管员工单完成时通知报修人。这一块用 FastAPI 的 BackgroundTasks 做异步发送即可不用引入消息队列。实际上一个简单的定时扫描加回调推送就能满足内部通知需求。6.2 门禁与人脸识别联动如果宿舍楼本身有门禁系统可以考虑把入住状态同步到门禁白名单。员工办退宿后门禁权限自动失效。技术方向上后端定时任务读取当天的入住变动记录把结果写入门禁系统的接口。这个场景比“人脸识别整套方案”现实得多——很多公司已经在用第三方门禁平台系统只需要对接现有 API而不是从零做一套识别算法。6.3 数据大屏与月度报表宿舍管理员喜欢看大屏。用 ECharts 做入住率趋势、各楼栋人员分布、水电费环比等展示。技术实现不复杂前端增加一个只读的大屏路由后端提供聚合统计接口即可。更有价值的是月度报表导出——把本月入住变动、费用数据、工单完成率汇总成一个 Excel用 Python 的 openpyxl 库坐在后端生成管理员每个月月底下载一次。我在实际使用中觉得报表导出比线上看数据更重要因为行政流程需要留档。这套系统开发下来真正耗费精力的部分不是增删改查而是把宿舍管理中的真实规则抽象成数据结构。如果你打算复刻我建议先反向梳理一遍自己公司的住宿流程把“换房怎么计费”“水电公摊怎么算”“临时借宿算不算占用床位”这些口径确定了再动手否则前端写得再漂亮业务规则对不上也是白搭。我个人的习惯是哪怕时间紧也先把流程清单写到一张 A4 纸上再开始建表写代码前期多花一天后期能省一周。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →