做校园电动车短租这个项目之前,我其实已经用 Python 写过不少小工具,但真正把 Python 后端和 Vue 前端串起来做成一个完整系统,还是头一回。项目标题叫“python基于vue的校园电动车短租系统的设计与实现”,技术栈锁定 django、flask、pycharm 这套组合。说白了,就是要做一个能在校园场景里跑起来的电动车租借平台:学生注册登录、浏览车辆、按小时或按天租车、生成订单、管理员审核车辆状态和订单记录。适合 Python 刚入门想做项目练手的同学,也适合毕业设计选了类似方向的读者参考。
这个项目真正的价值,不只是那几辆电动车,而是把一套典型的“前后端分离 + 数据建模 + 角色权限”业务闭环完整走通。做完以后,你收获的不只是一个能演示的系统,更是一整套可复用的开发套路:从需求拆解、表结构设计,到接口开发、前端页面渲染,再到联调排错。这篇文章我就把整个设计过程、踩过的坑、最终采用的方案全部摊开讲,代码可以直接抄,但思路比代码更值钱。
1. 项目整体设计与技术选型
1.1 需求拆解:一辆校园电动车短租系统到底要做什么
很多人拿到这种题目,第一反应是“不就是租车嘛”,真动手才发现连用户角色都没想清楚。校园短租场景和普通共享单车不一样的地方在于:车辆不是分散在路边,而是集中在几个车棚或停车场;用户是固定人群(学生和教职工);车辆有充电、保养、损坏赔偿等线下环节。所以系统必须覆盖三个角色。
- 学生/教师(租车人):注册登录、浏览车辆列表、按状态筛选(可用/充电中/维修中)、发起租车、查看自己的订单、还车、查看租车费用。
- 管理员(运营方):车辆信息增删改、车辆状态管理、订单审核与处理、统计每日营收和租出率。
- 系统本身:车辆可用性校验、订单状态流转(待支付→已租出→已还车→已完成)、费用计算。
我最初犯的错误是只做了前两个角色的基础增删改查,结果演示的时候发现一个关键场景没法闭环:用户把车还了,管理员不知道车停回哪个车棚,车辆状态也没有联动变化。所以后来在订单表里额外加了return_parking_id和actual_end_time字段,这才是短租系统和普通商品系统的本质区别——订单必须和车辆的物理状态联动。如果你正在设计类似的租赁系统,建议先把“租车”和“还车”两个动作的完整流程用文字写一遍,再动手建表。
1.2 Django 还是 Flask?我的真实取舍过程
标题里同时写了 django 和 flask,很多新手会纠结到底选哪个。我在这个项目里两种方案都折腾过,最后用的是 Django,理由很朴素:
| 对比项 | Django | Flask |
|---|---|---|
| 学习门槛 | 偏高,自带全家桶 | 低,自由组合 |
| 数据库操作 | ORM 自带,迁移方便 | 通常配 SQLAlchemy,需自己组装 |
| Admin 后台 | 自带 admin 界面 | 需自己写 |
| 项目结构 | 固定工程结构,适合中大型 | 灵活,适合小而快的服务 |
| 适合本项目的程度 | 订单、车辆、用户三个模块关联紧密 | 只做接口演示也够用 |
如果你只是想快速验证“Vue 前端调 Python 接口”这个流程,Flask 十分钟就能跑起来。但校园短租系统涉及用户认证、订单状态机、车辆库存判断,这些领域逻辑用 Django 的 ORM 和 admin 能省掉大量重复工作。我的建议是:做完整系统优先 Django,做接口 Demo 用 Flask。文章后续的代码示例以 Django 3.x + Python 3.9 为主,但会在关键位置标注 Flask 的对应写法。
提示:不要把“选框架”当成终点。真正决定项目成败的是数据模型和状态流转设计,框架只是表达这些设计的工具。
2. 数据库模型设计与后端核心实现
2.1 数据模型设计:四张核心表撑起整个业务
数据库设计是这类系统最容易被低估的部分。我第一版设计只有三张表(用户、车辆、订单),后来在联调阶段反复补字段,才明白短租系统真正要处理的是“状态 + 时间”两个维度的数据。最终核心表结构如下。
用户表(user)
| 字段 | 类型 | 说明 |
|---|---|---|
| username | CharField | 学号/工号 |
| password | CharField | 密码(哈希存储) |
| role | CharField | 0 学生 / 1 管理员 |
| balance | DecimalField | 账户余额,用于扣费 |
车辆表(vehicle)
| 字段 | 类型 | 说明 |
|---|---|---|
| vehicle_no | CharField | 车辆编号 |
| model_name | CharField | 车型(品牌、款式) |
| battery | IntegerField | 电量百分比 |
| status | IntegerField | 0 可租 / 1 已租 / 2 充电中 / 3 维修中 |
| parking_id | ForeignKey | 当前所在车棚 |
| price_per_hour | DecimalField | 小时单价 |
订单表(rental_order)
| 字段 | 类型 | 说明 |
|---|---|---|
| user | ForeignKey | 租车人 |
| vehicle | ForeignKey | 车辆 |
| start_time | DateTimeField | 租车时间 |
| end_time | DateTimeField | 预计还车时间 |
| actual_end_time | DateTimeField | 实际还车时间 |
| total_amount | DecimalField | 总费用 |
| status | IntegerField | 0 待支付 / 1 租用中 / 2 已完成 / 3 已取消 |
外加一张车棚表(parking),记录车棚编号、位置和可容纳车辆数。按需再补一张车辆维护记录表,用于维修审核。
设计时要注意一个关键约束:车辆 status 必须和订单 status 联动。当订单进入“租用中”,车辆必须标记“已租”;订单还车后,车辆标记“可租”并回写电量和停放位置。我建议在代码里用一个统一的封装函数处理这个状态切换,而不是在多个视图里各自修改,否则很容易出现车被重复租出的逻辑漏洞。
2.2 后端接口实现:从登录鉴权到订单闭环
后端采用 DRF(Django REST Framework),核心接口如下:
POST /api/auth/login 登录,返回 token GET /api/vehicles 车辆列表(支持状态筛选) POST /api/vehicles/{id}/rent 发起租车 POST /api/orders/{id}/return 还车并结算 GET /api/orders/my 我的订单 POST /api/orders/{id}/cancel 取消订单以租车接口为例,业务逻辑至少有四步:
- 校验当前车辆 status 是否为“可租”;
- 检查用户余额或信用状态(校园场景我做了余额预冻结);
- 创建 RentalOrder 记录,设置状态为“租用中”;
- 原子更新车辆 status 为“已租”。
由于第三步和第四步必须一起成功或一起失败,必须用事务包裹。Django 的处理方式是transaction.atomic():
from django.db import transaction @transaction.atomic def rent_vehicle(request, vehicle_id): vehicle = Vehicle.objects.select_for_update().get(id=vehicle_id) if vehicle.status != 0: return JsonResponse({'code': 1, 'msg': '车辆当前不可租'}) order = RentalOrder.objects.create( user=request.user, vehicle=vehicle, start_time=timezone.now(), status=1 ) vehicle.status = 1 vehicle.save() return JsonResponse({'code': 0, 'data': {'order_id': order.id}})这里select_for_update()是防并发租车的核心:两个用户同时点击租同一辆车时,数据库行锁会保证只有一个请求能通过状态校验。没有这行代码,并发测试下很容易产生“一辆车同时租给两个人”的事故。Flask 要做类似效果,可以配合 SQLAlchemy 的with_for_update(),逻辑一致。
还车接口的重点是费用计算。我的计费规则是:租期不满一小时按分钟折算,满一小时按小时计费,超时部分累加;如果车辆归还时电量低于 20%,额外收取充电补偿费。费用计算独立成函数,方便单元测试:
def calc_rental_fee(price_per_hour, start, end, battery_ratio): hours = (end - start).total_seconds() / 3600 fee = round(hours * price_per_hour, 2) if battery_ratio < 0.2: fee += 2.0 # 充电补偿 return fee这一步看起来简单,实际是项目里改动最多的模块,因为业务方会在演示前临时改计费规则。把规则收敛到一个函数里,改起来才不费力。如果你用 Flask,可以把同样的逻辑写成独立 service 函数,再注册到蓝图路由里。
3. 前端 Vue 部分与前后端联调
3.1 页面规划与关键组件设计
前端我用 Vue 2 + Element UI(如果现在重做,我会直接上 Vue 3 + Element Plus),核心页面有六个:
- 登录注册页
- 车辆列表页(卡片式展示车辆图片、电量、价格,支持状态筛选)
- 车辆详情页(可租时间、计费规则说明)
- 订单列表页(区分进行中/已完成/已取消)
- 个人中心(余额充值、个人信息)
- 管理后台(车辆管理、订单管理、统计报表)
车辆列表页是用户第一眼看到的东西,卡片设计有两个细节很影响体验:电量展示要用渐变进度条而不是纯数字;状态标签颜色要统一(可租=绿、已租=灰、充电=橙、维修=红)。这些细节在答辩或演示时被问的概率极高。
Vue 组件核心是车辆卡片和订单状态渲染。以车辆卡片为例:
<template> <el-card class="vehicle-card"> <div class="vehicle-title">{{ vehicle.model_name }}</div> <el-progress :percentage="vehicle.battery" :stroke-width="10" :color="batteryColor(vehicle.battery)" /> <div class="price">¥{{ vehicle.price_per_hour }}/小时</div> <el-tag :type="statusTagType(vehicle.status)"> {{ statusText(vehicle.status) }} </el-tag> <el-button type="primary" :disabled="vehicle.status !== 0" @click="handleRent(vehicle)">立即租车</el-button> </el-card> </template> <script> export default { methods: { batteryColor(val) { return val > 50 ? '#67c23a' : val > 20 ? '#e6a23c' : '#f56c6c'; }, statusTagType(val) { return ['success', 'info', 'warning', 'danger'][val]; }, statusText(val) { return ['可租', '已租', '充电中', '维修中'][val]; } } } </script>这种写法的好处是状态文案和颜色映射集中管理,后端改状态枚举时,前端只需要同步这一处映射即可。
3.2 前后端联调:axios 封装与跨域处理
前后端分离项目的联调环节是新手重灾区。我踩得最深的坑有两个。
跨域问题。Django 后端默认不允许浏览器直接跨域请求,需要在 settings 里配置django-cors-headers:
INSTALLED_APPS = [ # ... 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # ... ] CORS_ALLOW_ALL_ORIGINS = True # 本地开发用,上线需改为白名单Flask 则用flask-cors库,CORS(app)一行搞定。
axios 请求拦截器。登录后 token 需要挂到每个请求头上,统一封装请求模块能省掉无数重复代码:
import axios from 'axios'; const api = axios.create({ baseURL: process.env.VUE_APP_BASE_URL || 'http://127.0.0.1:8000/api', timeout: 10000 }); api.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Token ${token}`; } return config; }); api.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(error); } );注意 axios 拦截器里如果用this.$router,需要在组件外先引入 router 实例,否则拿不到路由对象。我一开始直接写成this.$router.push('/login'),结果控制台报错,排查半天才发现是作用域问题。
3.3 路由守卫与角色权限控制
短租系统的角色差异意味着路由权限也要区分,学生和管理员不应该看到同一套页面。Vue Router 的全局前置守卫是标准做法:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); const role = localStorage.getItem('role'); if (to.path !== '/login' && !token) { next('/login'); } else if (to.meta.requiresAdmin && role !== '1') { next('/'); } else { next(); } });管理后台的路由统一加上meta: { requiresAdmin: true }。前端隐藏入口只是体验层的拦截,真正的权限控制必须以后端接口校验为准。比如管理员的车辆删除接口,后端要验证request.user.role,不能只靠前端藏按钮。
4. 开发环境配置与 PyCharm 使用技巧
4.1 环境搭建:Python、Django、Vue 的版本搭配
这个项目最容易让人半途放弃的就是环境问题。我先给出一套自己验证过稳定的版本组合:
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.9 或 3.10 | 3.11+ 也兼容,但部分第三方库要选新版 |
| Django | 3.2 或 4.x | 3.2 长期维护,最稳 |
| Django REST Framework | 3.14+ | 配合 Django 使用 |
| Node.js | 16 或 18 | Vue 2 配 Node 14+,Vue 3 配 Node 16+ |
| Vue CLI / Vite | Vue CLI 4.x / Vite 4.x | 创建项目用 |
| MySQL | 5.7 或 8.0 | 也可以先用 SQLite 减轻负担 |
四个关键安装场景值得展开说。
Python 安装:从官网下载安装包时,一定记得勾选Add Python to PATH,这是新手反复踩坑的第一位。装完在终端执行:
python --version pip --version如果提示找不到命令,通常就是 PATH 没配好。Windows 用户也可以再用py -0查看已安装的版本。
PyCharm 安装:直接用 Community 版就够做这个项目,不需要找特殊激活方式。安装时勾选Add to PATH,创建桌面快捷方式。PyCharm 的项目解释器设置很关键——在内置 Terminal 里直接运行python时,要确认用的是项目解释器,而不是系统默认解释器,否则 pip 装包会装到另一个环境去。
Django 项目创建:
pip install django djangorestframework django-cors-headers django-admin startproject campus_rental cd campus_rental python manage.py startapp accounts python manage.py startapp vehicles python manage.py startapp orders创建完 app 后记得在settings.py的INSTALLED_APPS里注册,这一步漏了,迁移数据时会发现表根本建不出来。
Vue 项目创建:
npm install -g @vue/cli vue create frontend交互式选项里建议选Manual,勾选Router、Vuex,Vue 版本按需要选 2 或 3。创建完成后:
cd frontend npm install axios element-ui # Vue 2 # 或 npm install element-plus # Vue 3 npm run serve4.2 PyCharm 里的多项目联调配置
前后端分离项目意味着 PyCharm 里通常要同时开两个窗口:一个打开 backend 目录(Django),一个打开 frontend 目录(Vue)。有几点配置值得注意。
- Run Configuration:Django 项目的运行配置里,工作目录指向含
manage.py的文件夹,环境变量设置好DJANGO_SETTINGS_MODULE=campus_rental.settings。 - Terminal 环境:PyCharm 自带 Terminal 会继承项目解释器,直接在底部执行
pip install不会装错环境,这比单独开 CMD 稳得多。 - 代码检查:装一个 Flake8 插件,虽然有点洁癖,但多人协作时能少很多低级错误。
- 数据库面板:PyCharm Professional 自带 Database 工具,可以连 MySQL 看表结构;Community 版的话装 MySQL Workbench 或直接用命令行
python manage.py dbshell。
从 PyCharm 调试接口有个很实用的技巧:在视图函数里打断点,用 Postman 或浏览器触发请求,步进查看每一步的变量状态。排查订单状态不对的问题时,断点比print效率高一倍。
5. 常见问题与排查实录
5.1 高频问题速查表
这节把实际遇到的真实问题列成速查表,都是反复出现的:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前端请求 404 | 路由前缀写错,缺/api | 检查 axios baseURL 和 Django urls 配置 |
| 前端请求 500 | 后端视图异常,多半是空数据访问 | 看 PyCharm 控制台 traceback,定位具体行 |
| 车辆列表图片打不开 | 静态文件路径未配置 | Django 配MEDIA_URL+MEDIA_ROOT,开发环境用 serve 视图 |
| 租车后订单出现但车辆状态没变 | 事务未提交或逻辑分支漏了状态更新 | 检查是否绕过了统一的状态封装函数 |
| 登录后刷新页面就退出 | token 存储位置不对或未在请求头携带 | 确认localStorage和 axios 拦截器 |
| Vue 编译报内存溢出 | Node 内存限制 | 运行NODE_OPTIONS=--max-old-space-size=4096 |
5.2 必踩的三个坑与解决方法
坑一:时间字段的时区问题。Django 默认 USE_TZ=True,数据库存 UTC 时间,前端展示如果不转换,会出现“租车时间比北京时间少 8 小时”。解决方法是 settings 里设TIME_ZONE = 'Asia/Shanghai',前端再统一用dayjs格式化。
坑二:密码明文存储。如果注册功能里直接把密码存进数据库,答辩时被问到“密码安全吗”会很尴尬。Django 自带make_password和check_password,一定要用:
from django.contrib.auth.hashers import make_password, check_password user = User.objects.create(username=username, password=make_password(password))坑三:车辆并发租用。前面说过用select_for_update()解决。我写过一个并发测试脚本,用 20 个线程同时抢同一辆车,不加行锁时大约有 15% 的概率出现重复订单,加了select_for_update()之后稳定为 0 次。这个结论值得在你的项目里复现一次,印象会非常深刻。
5.3 答辩与演示前的专门准备
如果你的目标是课程设计答辩或毕业设计演示,有几个环节建议提前做好:
- 预置数据一定要丰富:至少 10 辆不同车型的车辆、3 个车棚、5 个历史订单,让页面看起来有真实感,也方便展示筛选和统计功能。
- 演示流程要有剧本:登录 → 浏览车辆 → 租车 → 查看订单 → 还车 → 查看费用变化,每一步对应一个界面变化。
- 准备好网络不佳时的兜底方案,比如本地 SQLite + 本地 Vue build 产物部署,避免现场演示翻车。
6. 扩展方向与个人体会
6.1 系统后续可以怎么扩展
做完基础版本后,我建议再加几个有意义的功能,既能充实简历,也能让项目真正走出课程作业的范畴。比如:节假日高峰期的车辆调度提醒,根据历史订单数据预判哪个车棚的车不够用;基于简单协同过滤的同学常选车型推荐;电动车充电桩占用的实时看板。这些方向都不需要引入太重的技术,却能让系统“活”起来。
如果想要在性能层面进阶,可以考虑给 Django 接口加上 Redis 缓存车辆列表,热数据查询响应能从几百毫秒降到几十毫秒。Vue 端也可以用 keep-alive 缓存列表页滚动位置,提升浏览体验。这些优化在简历上写起来很好看,答辩也能展示你“会用工具解决问题”而不是只会写 CRUD。
6.2 一点个人体会
我个人在这个项目里最大的收获,其实是学会了“先想清楚状态流转再写代码”。电动车短租表面上是个 CRUD 项目,但订单与车辆状态的联动、费用计算的边界条件、并发租车的数据一致性,这些才是一个系统真正有价值的部分。如果你也想做类似的校园服务类系统,建议从一张订单状态流转图开始画起,把自己代入运营方的角色去推演业务流程,而不是一上来就敲代码。
最后再分享一个小技巧:开发阶段把 Django 的DEBUG=True和 Vue 的 devtools 配合使用,出错时两端的信息一对照,基本能把 90% 的问题定位在十分钟内。项目写完后,把DEBUG改成False,配好ALLOWED_HOSTS,再单独部署一次生产环境——哪怕只是在本机跑 Nginx + uWSGI,你也会发现“能跑”和“能部署”之间还有一道坎。跨过它,这个项目才算真正做完。