停车管理系统这个题目,在社区、园区、写字楼里越来越常见。最近我刚好把一个基于Vue和Python的社区车辆停车场车位管理系统从头到尾做了一遍,从需求梳理到前后端联调再到部署上线,踩了不少坑,也沉淀了一些可以直接复用的经验。这篇文章就把整个项目的关键点拆开讲一遍,尤其是车位状态管理、车辆进出场流程、前端车位地图渲染和并发控制这几个核心环节,给正在做类似项目的同学一个能直接参考的落地路线。
系统本身解决的是很具体的问题:物业需要一个能实时看到每个车位占用情况、能记录车辆进出时间、能算停车费、能管理固定车位和临时车辆的管理后台;车主希望能快速找到空车位,最好能提前预约;而开发者需要一套清晰、可维护、能应对一定并发压力的前后端分离方案。Vue负责交互体验,Python负责业务逻辑和数据,两者通过RESTful API通信,是目前这类管理系统的典型架构。
1. 项目整体拆解:从需求到技术选型
1.1 这个系统到底要解决哪些问题
做这类系统之前,我习惯先把角色和场景捋清楚。社区停车场系统里主要有三种角色:管理员(物业人员)、固定车主(月租/年租)、临时车主。三种角色关心的事情完全不一样。
管理员要看到的是:总共有多少车位、当前空闲多少、每个车位的归属状态(固定/临时/预约)、某辆车停了多久、应收多少钱、有没有车超时滞留。固定车主关心的是:自己能不能顺利进场、车位有没有被占用、每个月缴费记录是否清晰。临时车主关心的更简单:有没有空位、怎么缴费出场。
对应的核心功能就出来了:车位管理(增删改查、车位状态映射)、车辆管理(车牌绑定、车主信息)、进出场管理(入场记录、出场结算)、收费规则配置(按时段计费、按月租计费)、预约功能(可选)、统计报表(日营收、车位利用率)。如果还想做得更像商用系统,可以加道闸对接、摄像头车牌识别,但MVP阶段可以先用手动录入车牌或者扫码入场的方式,把系统本身的闭环跑通。
我在设计时特别注意了“车位状态”这个概念。一个车位不是只有“空闲/占用”两种状态,还应该有“预约中”“维修中”“固定车位已绑定”等状态。这些状态直接影响前端地图的颜色显示和后端分配逻辑。如果一开始只做两个状态,后续加预约功能时整个数据库和接口都要返工,非常痛苦。
1.2 为什么是Vue+Python而不是其他组合
这个选型现在看几乎是最稳的。前端用Vue,生态成熟、学习曲线平缓、组件化开发适合这种以“列表+表单+地图+图表”为主的管理系统。Python后端我选了Flask,而不是Django,原因很简单:这个项目逻辑不复杂,Flask轻量、灵活,配合SQLAlchemy做ORM,写起来很快。如果预期系统会非常庞大、有复杂的后台权限体系,Django的admin和内置用户系统会很省事,但这类社区停车系统通常不需要那么重的框架。
前端Vue方面,我用的是Vue 3 + Vite + Element Plus。Vue 3的组合式API(Composition API)在处理车位地图这种需要维护大量状态、定时刷新数据的页面时,比选项式API清晰很多。Element Plus提供了现成的表格、表单、对话框、消息提示,省掉大量基础UI工作。构建工具用Vite,本地开发热更新非常快,比改一行等三秒的Webpack体验强太多。
数据库选了MySQL。项目早期用SQLite也能跑,但停车系统涉及并发写入(比如多个车同时扫码进场),MySQL的事务机制更可靠,而且数据量大以后,按时间查询进出记录也更快。
选型这件事上我的经验是:不要为了炫技引入新框架。你可以用Django、Spring Boot甚至Node.js做后端,前端也可以用React,这些都能实现,但Vue+Python的组合在社区、中小型项目中维护成本最低,招人也好招,遇到问题社区答案多。
1.3 项目目录结构怎么规划
前后端分离项目,目录规划直接影响协作效率。我的做法是单仓库双目录,结构如下:
parking-system/ ├── backend/ │ ├── app.py # Flask入口 │ ├── models.py # 数据库模型 │ ├── api/ │ │ ├── auth.py # 登录鉴权 │ │ ├── vehicles.py # 车辆管理接口 │ │ ├── spots.py # 车位管理接口 │ │ ├── records.py # 进出场记录接口 │ │ └── dashboard.py # 统计报表接口 │ ├── utils/ │ │ ├── fee.py # 计费规则引擎 │ │ └── db.py # 数据库连接 │ └── requirements.txt ├── frontend/ │ ├── src/ │ │ ├── views/ │ │ │ ├── Dashboard.vue │ │ │ ├── ParkingMap.vue │ │ │ ├── VehicleManage.vue │ │ │ └── Records.vue │ │ ├── components/ │ │ │ ├── SpotGrid.vue │ │ │ └── FeeDialog.vue │ │ ├── api/ │ │ │ └── request.js │ │ └── store/ │ │ └── user.js │ └── package.json └── deploy/ ├── nginx.conf └── parking.service这个结构的核心思路是:后端按资源模块划分API,前端按页面视图划分组件,数据库模型独立成文件。这样开发时一个人可以并行处理多个模块,不用在同一个文件里频繁冲突。后端的utils/fee.py单独抽出来,是因为计费规则大概率需要频繁调整,独立成模块方便单元测试,也避免以后改计费的时候动主逻辑代码。
2. 后端核心实现:数据模型与进出场流程
2.1 数据模型设计,这几张表最关键
停车系统的数据模型其实不复杂,核心是四张表:车位表、车辆表、进出记录表、订单/缴费表。外加一张用户表,用于管理员登录和车主关联。
车位表的精髓在于状态字段。我的设计如下:
class ParkingSpot(db.Model): id = db.Column(db.Integer, primary_key=True) code = db.Column(db.String(20), nullable=False, unique=True) # 车位编号,如 A-001 area = db.Column(db.String(50), default='A区') # 所属区域 status = db.Column(db.String(20), default='free') # free/occupied/reserved/maintenance bind_vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id'), nullable=True) # 固定绑定车辆 update_time = db.Column(db.DateTime, onupdate=datetime.now)status字段用字符串而不是布尔值,就是为了一开始就把“预约中”“维修中”这些状态纳入体系。bind_vehicle_id记录固定车位绑定的车辆,如果某辆车是月租车主,它的车位被绑定后,其他车辆无法占用。
车辆表的关键字段包括车牌号、车主姓名、手机号、车辆类型(固定/临时)、月租到期时间。车牌号是业务上的唯一标识,要加唯一索引。这里有个细节:车牌号有新能源绿牌(7位)和普通蓝牌(7位,但前两位是省份简称),还有港澳车辆等特殊情况,字段长度建议留到10位,类型用字符串,不要用数字。
进出记录表是所有计费的依据。每次车辆进场生成一条记录,记录入场时间、入场时的车位号、车辆ID;出场时更新出场时间和费用。这张表会快速增长,建议按月份做分区,或者至少加entry_time索引,否则一年后查询会明显变慢。
订单表用于临时车辆的缴费结算,字段包括记录ID、车牌号、停车时长、费用、支付时间、支付方式。固定车辆按月租结算,不产生日订单,但需要单独的缴费记录表来跟踪月租续费情况。
这里我特别想分享一个设计教训:不要把“当前占用关系”和“历史记录”混在一张表里。我最初为了方便,准备在车位表里直接写current_vehicle_id,后来发现一旦车辆换位、维修等情况出现,历史的占用关系全部丢失,统计报表根本没法做。正确的做法是车位表只管状态,进出记录表才管“谁在哪段时间用了哪个车位”。
2.2 车辆进场出场的接口逻辑
进场接口是整个系统最核心的接口。车辆入场时,系统要做的事情按顺序排列一下:
- 根据车牌号查询车辆信息,判断是固定车还是临时车;
- 找一个空闲的、未被预约(或该车已预约)的车位;
- 将车位状态改为占用,绑定当前车辆;
- 创建一条进场记录;
- 返回车位分配结果,前端打开道闸或显示引导信息。
这一连串操作必须在数据库层面保证原子性。否则两个车同时请求“找空闲车位”,可能拿到同一个车位号,造成一坑两车。
我推荐用MySQL的事务配合SELECT ... FOR UPDATE来做。先锁定车位表的空闲记录,再更新状态。伪代码如下:
def assign_spot(vehicle_id, reserved_spot_id=None): try: # 如果该车辆有预约,优先分配预约车位 if reserved_spot_id: spot = ParkingSpot.query.filter_by( id=reserved_spot_id, status='reserved' ).with_for_update().first() else: spot = ParkingSpot.query.filter_by( status='free' ).with_for_update().first() if not spot: return None, "暂无可分配车位" spot.status = 'occupied' spot.bind_vehicle_id = vehicle_id record = EntryRecord( vehicle_id=vehicle_id, spot_id=spot.id, entry_time=datetime.now(), status='unfinished' ) db.session.add(record) db.session.commit() return spot.code, None except Exception: db.session.rollback() return None, "内部错误"使用with_for_update()后,同一时间只有一个事务能读取到那行空闲车位,其他请求会被阻塞,直到当前事务提交。这是避免“超卖车位”的最基本手段。如果你的系统跨度很大,比如多个道闸同时入场并发极高,还可以引入Redis做一个预占锁,但实际社区场景下,SQL行锁已经足够。
出场逻辑相对简单。根据车牌查最近一条未完成记录,计算停车时长和费用,把费用信息返回给前端。临时车缴费后,更新记录状态为已完成,释放车位(状态改为空闲,bind_vehicle_id置空)。固定车一般不需要实时缴费,记录出场信息后,每月统一结算。
2.3 计费规则的实现技巧
计费是这类系统里最容易出bug的地方。社区停车场的计费规则通常很绕,比如“前两小时免费,超过按每小时3元,单日封顶20元,跨天重新计算”,或者“夜间停车有优惠价”。
我在utils/fee.py里设计了一个简单的规则引擎,用优先级列表的方式处理:
def calc_fee(entry_time, exit_time, vehicle_type): if vehicle_type == 'fixed': return 0 # 月租车不按次计费 duration_hours = (exit_time - entry_time).total_seconds() / 3600 # 前2小时免费 billable_hours = max(0, duration_hours - 2) fee = billable_hours * RATE_PER_HOUR # 单日封顶 daily_cap = min(fee, DAILY_CAP) # 如果有过夜优惠,再计算优惠逻辑 if entry_time.hour >= 20 and exit_time.hour <= 8: fee = min(fee, NIGHT_FEE) return round(max(daily_cap, fee), 2)我的经验是按“规则优先级链”实现,而不是写一个大的if-else。每条规则返回一个费用值,然后用一个函数把“免费规则结果、封顶规则结果、跨天结果”取最大值或最小值组合。这样计费逻辑以后要加优惠券、节假日调价时,只需要新增一条规则函数,不需要改核心逻辑。
计费脚本一定要补单元测试。之前我写过一次计费逻辑,自以为正确,结果跨天场景下多算了3个小时的费用。后来补了一组测试用例,覆盖入场当天23:55、次日00:10这样的边界情况,才真正放心。
3. 前端交互细节:Vue实现可视化车位地图
3.1 车位地图组件怎么画才直观
前端部分最出效果的是车位地图页面。用户进系统后第一眼看到的就是一个类似实际停车场布局的示意图,车位用色块表示,绿色空闲、红色占用、黄色预约、灰色维修。
实现方式超过三种:CSS Grid布局、SVG绘制、Canvas绘制。我的选择是CSS Grid + 固定比例布局,原因是车位地图通常是规整的横纵排列,用Grid定义行列非常直观,颜色状态通过绑定类名控制,不用像Canvas那样每次重绘。
组件结构大致如下:
<template> <div class="parking-lot" :style="lotStyle"> <div v-for="spot in spots" :key="spot.id" class="parking-spot" :class="statusClass(spot.status)" @click="showSpotDetail(spot)" > {{ spot.code }} </div> </div> </template> <script setup> import { computed } from 'vue' const props = defineProps({ spots: Array, rows: Number, cols: Number }) const lotStyle = computed(() => ({ gridTemplateColumns: `repeat(${props.cols}, 1fr)` })) const statusClass = (status) => ({ 'spot-free': status === 'free', 'spot-occupied': status === 'occupied', 'spot-reserved': status === 'reserved', 'spot-maintenance': status === 'maintenance' }) </script> <style scoped> .parking-lot { display: grid; gap: 8px; background: #f5f5f5; padding: 16px; border-radius: 8px; } .parking-spot { height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 4px; color: #fff; font-size: 13px; cursor: pointer; } .spot-free { background: #67c23a; } .spot-occupied { background: #f56c6c; } .spot-reserved { background: #e6a23c; } .spot-maintenance { background: #909399; } </style>真正部署时你会发现事情没那么简单。社区停车场的车位不一定是整齐的矩阵,有斜列、有边缘车位、还有充电桩专用位。这时候Grid的grid-column: span 2或grid-row: span 1可以模拟部分不规则布局,但如果场地实在复杂,建议还是用SVG绘制底图,把每个车位定义成可点击的<rect>元素,坐标自由度高得多。
3.2 状态实时刷新,别用傻轮询也不要靠手动刷新
车位状态实时性是这个系统体验的关键。我第一版用的是最粗暴的方式:页面加载时请求一次数据,点击刷新按钮再请求一次。结果就是物业那边经常抱怨“手机上明明显示有空位,过去一看满了”。
后来改成轮询,每5秒请求一次车位列表接口,效果立竿见影,但心里计算了一下,如果10个管理端页面同时开着,后端每秒要扛几十个请求。好在这个系统后端足够轻量,MySQL扛得住,但长期看不太优雅。
更合理的方式是WebSocket或者SSE。如果后端接口和前端是同一套部署,用WebSocket推送车位状态变更事件是最优解。进场、出场、预约操作触发后,后端主动往所有连接推送新状态,前端收到后直接更新对应车位的数据,不需要整页轮询。
不过开发WebSocket需要引入额外的通信协议和鉴权机制,如果项目工期紧张,我建议退一步:用“轮询+事件更新”混合方案。前端的车位地图默认为10秒轮询,但如果当前用户正在执行某个操作(比如点击预约车位),操作成功后立即本地更新地图并触发一次主动刷新。这样既保证了数据新鲜度,又不会无脑压榨后端。
3.3 表单校验和交互细节
前端看起来简单,实际上细节决定成败。我整理几个做这类系统最容易踩的用户体验问题:
车牌号输入:中国大陆的车牌由省份简称+字母+数字组成,中间没有空格。用户在手机端输入时很容易打错,我的处理方式是前端加一个正则校验:/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z][A-HJ-NP-Z][A-Z0-9]{5,6}$/。这个正则覆盖了普通车牌和新能源车牌,但注意不要把字母I和O放进去。
入场和出场界面要分开设计。物业的操作用户年龄偏大,字号要大,按钮语义要明确。入场界面就一个大按钮“扫牌入场”,扫码后自动识别车牌,显示出车牌号和推荐车位,点击确认才真正开闸。出场界面突出收费金额,显示停车时长,如果超时或异常订单,用醒目的红色提示。
车位预约的交互要注意防重复提交。用户点击某个空闲车位弹出预约确认框后,如果连点两次确认,会发送两个请求。后端虽然可以用事务兜底,但前端也应该在请求发出后立刻给按钮加上loading状态,禁止二次点击。
不要低估“空状态”的设计。停车场空的时候,页面一片绿色,看起来没问题。但当车位全部满时,用户需要一眼看到“所有车位已满,请排队等候”的提示。我加了一个全局横幅,车位使用率达到100%时显示红色提示,物业那边反馈这个功能非常有用。
4. 实操过程与核心环节实现
4.1 从零跑通前后端联调
我搭建项目的时间线大概是这样的:第一天搭好后端Flask骨架和数据库四个表,第二天写完进出门和车位管理的接口,第三天前端Vue脚手架搭好,第四天开始联调。如果是一个人有全栈经验,三天内能跑通主流程,五天做完整套管理系统比较合理。
联调阶段最重要的事是先定好接口契约,不要边写前端边让后端改字段。我在项目根目录维护了一个api.md,每个接口的URL、请求参数、返回格式都写清楚。后端用Flask的Blueprint统一接口前缀为/api/v1,前端封装一个request.js,把baseURL和错误处理统一管理。
// request.js import axios from 'axios' import { ElMessage } from 'element-plus' const service = axios.create({ baseURL: '/api/v1', timeout: 10000 }) service.interceptors.response.use( response => response.data, error => { ElMessage.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } )联调环节最耗时的不是写接口,而是对数据格式的沟通成本,比如时间字段是字符串还是时间戳,分页参数叫什么名字。最好后端直接在created_time这样的字段上统一返回驼峰命名,前端不需要再写一堆字段映射函数。
4.2 权限控制:管理员和普通用户不能看同一个界面
停车系统的权限分为两个等级:管理员可以管理车位、查看财务报表、重置其他用户的密码;普通保安/物业人员只能使用进出场操作和查看当日记录。因为用户角色少,我没引入复杂的RBAC框架,直接用JWT登录后返回一个role字段,前端根据角色路由控制页面入口。
后端每个API要记得做权限装饰器校验。我的做法是:
def role_required(*roles): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): token = request.headers.get('Authorization') payload = verify_jwt(token) if payload.get('role') not in roles: return jsonify({'message': '无权限访问'}), 403 return func(*args, **kwargs) return wrapper return decorator这里有一个实际开发中的教训:仅靠前端隐藏菜单根本防不住懂点技术的人直接调接口。如果只用v-if控制“停车记录”按钮的显示,保安也可以通过浏览器控制台手动调用接口查看营收明细。所以后端接口一定要做真正的权限校验,前端隐藏只是用户体验层面的优化。
4.3 车位地图与后端数据的绑定
把前端的车位地图和后端数据打通时,容易遇到一个性能问题:地图页打开时一次性加载所有车位的数据,如果社区有300个车位,这个接口返回的JSON大概在几百KB,不算大,但每次都全量传输也浪费资源。
我的优化方案是按区域懒加载。前端先展示区域列表,用户点击某个区域,才请求该区域的车位数据。这样首屏加载轻松了很多,而且物业通常只关注自己负责的区域,体验反而更好。
另外要注意车位地图的响应式适配。保安亭里的电脑屏幕可能只有1366x768,而管理者的办公电脑是1920x1080。我用CSS的vmin单位做车位格子的最小尺寸,确保小屏幕下不至于挤在一起。
5. 常见问题与排查技巧实录
5.1 并发抢车位导致的“一坑两车”
项目上线后的第一个高峰期就暴露了并发问题。当时是周末下午,小区迎来入场高峰,多位保安同时操作入场,结果出现两个车被分配到了同一个车位。
排查过程是这样的:我先看后端日志,发现两个请求几乎同时读到了空闲车位表的同一条记录。MySQL默认的可重复读隔离级别下,两个事务可能同时查到一条空闲记录,然后都尝试更新,其中一个会因行锁堵塞,但另一个已经提交,第一个就获取了过期的数据。
解决办法就是前面提到的with_for_update()。另外我还加了一个兜底约束:车位表状态变更时,在应用层先校验当前状态是否为期望状态,比如:
update_count = ParkingSpot.query.filter_by( id=spot_id, status='free' ).update({'status': 'occupied'}) if update_count == 0: return None, "车位已被占用"这个写法利用的是SQLAlchemy的乐观锁思路,即使事务里读到了旧数据,更新时条件不满足也不会成功。双保险之后,这类问题再没出现过。
5.2 车牌识别不准导致的入场混乱
我们最初接了一个第三方的车牌识别API,识别率号称99%,实际用在老旧的蓝牙闸机上,经常会漏掉最后一个字符,或者把“0”识别成“O”。
我的排查心得是:识别结果千万不要直接存库。入场时先弹出确认框,让操作员肉眼核对一下识别结果,如果有误,手动修改后再确认。虽然多了这一步操作,但大幅减少了后续出场时车牌对不上的麻烦。如果做无感通行,这一步不能省,否则会产生大量“幽灵订单”。
出场时如果找不到匹配的入场记录,可以加一个“无记录出场”的兜底流程。操作员手动输入车牌后,系统查不到进场记录就提示选择“手动建单”,直接填写入场时间估算费用。小区里的车偶尔会从另一个门进场而没有同步到系统,这个兜底功能是实际运营中必须有的。
5.3 跨天计费的边界问题
计费模块上线后,接到了几个车主投诉,说停车费扣多了。我统计了一下投诉案例,全部集中在“晚上20点进入,次日早上8点前离开”的过夜场景。最初的计费逻辑是纯按小时累积,11小时\times3元_2小时免费 = 27元,但物业的规则是过夜停车封顶10元。
我在fee.py里加了过夜费用计算分支后,测试用例覆盖了这些场景,才算彻底解决。这类业务规则的坑很难在生产环境暴露前都预想到,建议上线前找物业经理坐下来,把所有的收费规则一条条念出来,转换成测试用例,写死在测试代码里,比上线后修补强得多。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 两个车同时拿到一个车位 | 并发事务未锁定行 | 打开MySQL日志查看同一时间段的更新语句 | 增加with_for_update()+ 乐观锁条件更新 |
| 出场时找不到入场记录 | 车牌识别不一致或出口入口数据不同步 | 查看识别日志和车辆表匹配情况 | 增加人工补单入口,入场时确认车牌 |
| 车位地图状态长时间不变 | 轮询未定时触发或状态接口缓存 | 打开浏览器网络面板,查看请求频率 | 检查轮询组件生命周期,用onMounted开启定时器 |
| 前端接口报401 | Token过期或Redis重启导致Session丢失 | 查看JWT过期时间设置 | 增加刷新Token机制,或延长过期时间 |
| MySQL连接数被打满 | 接口高并发且未使用连接池 | 查看show processlist | 配置SQLAlchemy连接池pool_size和max_overflow |
5.5 一个容易被忽视的性能瓶颈
系统上线一个月后,报表页面的加载时间从最初的不到一秒变成了三秒以上。排查发现是统计接口做了全表COUNT和SUM,而进出记录表已经有几十万条数据。解决办法是增加一个daily_income汇总表,每天晚上定时任务统计前一天的数据写入汇总表,报表页只需要查汇总表,不需要扫全表。
这个优化只花了半小时,但效果非常明显。我做这类系统的一贯原则是:尽快让项目跑起来,但心里要给未来数据量留一条扩容的路,汇总是最省事的办法。
6. 部署上线与后续扩展方向
6.1 最简单的部署方式
这个系统部署不算复杂。后端用Gunicorn启动多个工作进程,前面挂Nginx做反向代理,前端构建后的静态文件直接交给Nginx托管,API请求通过/api路径转发到后端端口。我的Nginx配置核心片段如下:
server { listen 80; server_name parking.example.com; root /opt/parking/frontend/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }try_files那行是Vue Router history模式必须配置的。如果忘了配,用户直接刷新某个子路由页面(比如/dashboard)时,Nginx会返回404,因为服务器上根本没有那个文件路径。
6.2 数据必须做定期备份
停车系统的数据库丢失的后果是灾难性的:所有历史进出记录、计费记录、用户信息全部没掉。我建议至少每天自动备份一次,保留最近30天的备份。最简单的方案是写一个cron任务定时执行mysqldump。
mysqldump -u parking -p****** parking_db > /data/backup/parking_$(date +%F).sql find /data/backup -name '*.sql' -mtime +30 -exec rm {} \;备份的恢复也要演练一次,不要等事故发生了才第一次尝试恢复。我做过一次模拟恢复,结果发现备份文件里的字符集有问题,中文全部变成乱码,后来加了--default-character-set=utf8mb4参数才解决。
6.3 后续可以扩展的功能
这套系统做好后,业务方马上提出了一堆新需求,比较有代表性的几个方向是:
预约停车:车主通过微信小程序或APP提前预约某个时段的固定车位,系统在预约时段内将该车位锁定,超时未到则释放。实现方案是增加一张预约表,并在车位分配时优先检查预约关系。
计费规则配置化:把基础免费时长、单价、封顶价等参数做到管理后台可配置,不用改代码。核心是重新设计fee.py,把规则配置存进数据库。
数据大屏:在物业办公室放一个电视展示实时车位利用率、今日营收、车辆进出排行,这个用Vue的ECharts包可以快速实现,数据来自统计接口。
多功能道闸对接:目前大部分社区的停车系统都需要和道闸厂商的设备对接,协议五花八门,但大多数支持HTTP回调或者串口指令。接入时要注意设备厂商的SDK版权和授权问题,尽量选有开放API的厂商。
我个人在做完这个项目后的最大体会是:停车管理系统看起来是一个非常“传统”的业务系统,但它其实绕不开并发、状态管理、数据一致性这些基本功。这个项目麻雀虽小,五脏俱全,非常适合作为前端转全栈、或者想练手真实业务系统开发的练手项目。如果你正在规划类似的系统,建议先把数据模型和状态流转想清楚,再做界面和接口,顺序反了会返工到怀疑人生。
最后分享一个小技巧:开发阶段直接把/api请求通过Vite的proxy代理到本地后端,别开跨域。生产环境把前后端放在同一个Nginx下,也能避免跨域问题。跨域配置能不动就不动,少一个变量,系统就少一分出错的可能。