大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Python的微信小程序共享单车系统(FastAPI+Vue3) 。
项目介绍
共享单车作为城市绿色出行的重要组成部分,能够有效衔接公交、地铁等大运量公共交通,缓解短距离出行不便和道路拥堵问题。传统共享单车平台功能集中、接口封闭,难以直接作为高校实践教学和中小规模运营场景的完整解决方案。针对这一现状,本文设计并实现了一套面向实际业务闭环的微信小程序共享单车系统。
系统采用前后端分离的三层架构。用户端基于微信小程序开发,提供地图找车、扫码解锁、骑行计时、规范还车、余额充值、押金管理、骑行卡购买、订单查询、故障报修、骑行评价以及个人资料与密码维护等功能;管理端基于 Vue3、Vite 与 Element Plus 构建,实现用户与车辆管理、停车点维护、计费规则与骑行卡配置、订单与资金流水查询、故障处理、公告发布以及带统计图表的运营首页;服务端采用 Python 语言和 FastAPI 框架,结合 SQLAlchemy 2.x 与 MySQL 8 完成数据持久化,使用 JWT 实现管理员与普通用户的身份鉴别。
数据库设计遵循第三范式,建立 db_bike 库及 t_admin、t_user、t_bike、t_ride_order 等 12 张业务表。骑行计费采用“起步价 + 超时按分钟计费 + 单次上限”模型,并结合 Haversine 公式估算里程;支付环节支持余额支付、骑行卡抵扣和模拟微信支付。测试表明,系统主要业务流程运行稳定,接口响应及时,能够满足毕业设计对完整性、规范性与可演示性的要求,对同类校园或社区共享出行系统具有参考价值。
源码下载
链接: https://pan.baidu.com/s/1X2m0WnWBA8Vfoa4IzfehlQ?pwd=1234
提取码: 1234
系统展示
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
核心代码
""" 骑行卡服务 """ from sqlalchemy.orm import Session from app.exceptions import BusinessException from app.models import RideCard def page_list(db: Session, current: int, size: int, keyword: str = None): """分页查询骑行卡""" query = db.query(RideCard) if keyword: query = query.filter(RideCard.name.like(f"%{keyword}%")) total = query.count() records = ( query.order_by(RideCard.create_time.desc()) .offset((current - 1) * size) .limit(size) .all() ) return records, total def save_card(db: Session, data: dict) -> None: """保存骑行卡""" card_id = data.get("id") if card_id: card = db.query(RideCard).filter(RideCard.id == card_id).first() if not card: raise BusinessException("骑行卡不存在") for key, val in data.items(): if key != "id" and val is not None: setattr(card, key, val) else: card = RideCard( name=data["name"], card_type=data["card_type"], valid_days=data.get("valid_days", 30), total_times=data.get("total_times"), free_minutes=data.get("free_minutes", 30), price=data["price"], description=data.get("description"), status=data.get("status", 1), ) db.add(card) db.commit() def delete_card(db: Session, card_id: int) -> None: """删除骑行卡""" card = db.query(RideCard).filter(RideCard.id == card_id).first() if card: db.delete(card) db.commit() def list_active(db: Session): """查询上架骑行卡""" return ( db.query(RideCard) .filter(RideCard.status == 1) .order_by(RideCard.price.asc()) .all() )<template> <div class="page-container"> <div class="search-bar"> <el-select v-model="query.status" placeholder="处理状态" clearable> <el-option v-for="(item, key) in faultStatusMap" :key="key" :label="item.label" :value="Number(key)" /> </el-select> <el-button type="primary" @click="loadData"><el-icon><Search /></el-icon> 搜索</el-button> </div> <div class="table-card"> <el-table :data="tableData" v-loading="loading" stripe border> <el-table-column prop="id" label="ID" min-width="70" /> <el-table-column prop="userId" label="用户ID" min-width="80" /> <el-table-column prop="bikeId" label="单车ID" min-width="80" /> <el-table-column prop="faultType" label="故障类型" min-width="120" /> <el-table-column prop="description" label="描述" min-width="180" show-overflow-tooltip /> <el-table-column label="状态" min-width="90"> <template #default="{ row }"> <el-tag :type="faultStatusMap[row.status]?.type" size="small"> {{ faultStatusMap[row.status]?.label }} </el-tag> </template> </el-table-column> <el-table-column label="报修时间" min-width="170"> <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template> </el-table-column> <el-table-column label="操作" min-width="120" fixed="right"> <template #default="{ row }"> <el-button v-if="row.status !== 2" type="primary" link @click="openHandle(row)">处理</el-button> <el-button type="info" link @click="showDetail(row)">详情</el-button> </template> </el-table-column> </el-table> <div class="pagination-wrap"> <el-pagination v-model:current-page="query.current" v-model:page-size="query.size" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" background @size-change="loadData" @current-change="loadData" /> </div> </div> <!-- 处理弹窗 --> <el-dialog v-model="handleVisible" title="故障处理" width="520px" destroy-on-close> <el-form ref="formRef" :model="handleForm" label-width="100px"> <el-form-item label="处理状态"> <el-radio-group v-model="handleForm.status"> <el-radio :value="1">处理中</el-radio> <el-radio :value="2">已处理</el-radio> </el-radio-group> </el-form-item> <el-form-item label="回复内容"> <el-input v-model="handleForm.reply" type="textarea" :rows="4" placeholder="请输入处理回复" /> </el-form-item> </el-form> <template #footer> <el-button @click="handleVisible = false">取消</el-button> <el-button type="primary" :loading="submitLoading" @click="submitHandle">确定</el-button> </template> </el-dialog> <!-- 详情弹窗 --> <el-dialog v-model="detailVisible" title="故障详情" width="560px" destroy-on-close> <el-descriptions :column="1" border v-if="detailRow"> <el-descriptions-item label="故障类型">{{ detailRow.faultType }}</el-descriptions-item> <el-descriptions-item label="描述">{{ detailRow.description }}</el-descriptions-item> <el-descriptions-item label="图片"> <div v-if="detailRow.images" class="image-list"> <el-image v-for="(img, idx) in detailRow.images.split(',')" :key="idx" :src="img" :preview-src-list="detailRow.images.split(',')" fit="cover" style="width: 80px; height: 80px; margin-right: 8px; border-radius: 6px" /> </div> <span v-else>-</span> </el-descriptions-item> <el-descriptions-item label="回复">{{ detailRow.reply || '-' }}</el-descriptions-item> <el-descriptions-item label="处理时间">{{ formatDateTime(detailRow.handleTime) }}</el-descriptions-item> </el-descriptions> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import { ElMessage } from 'element-plus' import request from '@/utils/request' import { formatDateTime, faultStatusMap } from '@/utils/date' /** * 故障报修管理页面 */ const loading = ref(false) const submitLoading = ref(false) const handleVisible = ref(false) const detailVisible = ref(false) const detailRow = ref(null) const tableData = ref([]) const total = ref(0) const query = reactive({ current: 1, size: 10, status: null }) const handleForm = reactive({ id: null, status: 2, reply: '' }) async function loadData() { loading.value = true try { const res = await request.get('/admin/fault/page', { params: query }) tableData.value = res.data.records total.value = res.data.total } finally { loading.value = false } } /** 打开处理弹窗 */ function openHandle(row) { handleForm.id = row.id handleForm.status = row.status === 0 ? 1 : 2 handleForm.reply = row.reply || '' handleVisible.value = true } /** 提交处理 */ async function submitHandle() { submitLoading.value = true try { await request.put(`/admin/fault/handle/${handleForm.id}`, null, { params: { status: handleForm.status, reply: handleForm.reply } }) ElMessage.success('处理成功') handleVisible.value = false loadData() } finally { submitLoading.value = false } } /** 查看详情 */ function showDetail(row) { detailRow.value = row detailVisible.value = true } onMounted(() => loadData()) </script> <style scoped> .image-list { display: flex; flex-wrap: wrap; gap: 8px; } </style>