ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python与Vue构建交通旅游订票系统:从Django到Flask的前后端分离实践

Python与Vue构建交通旅游订票系统:从Django到Flask的前后端分离实践 1. 从订票需求到系统落地这个项目到底要解决什么问题先说结论这不是一个普通的课程设计作业而是一个把“旅行预订”这条完整业务链路拆开、再用主流技术栈逐步复刻的真实项目。我当初决定做这个Python基于Vue的交通旅游订票系统核心动机很简单——市面上的旅游网站要么只做机票、要么只做酒店能把景点门票、酒店、航班、旅行计划串在一起的完整案例非常少。而单独看每一个模块都不难难的是让它们在一套系统里协同工作。这个系统的目标用户有两类一类是普通游客需要搜索景点、预订门票、订酒店、查航班、生成自己的旅行计划另一类是后台管理员需要维护景点信息、酒店房态、航班班次、订单状态。游客端和后台管理端分开设计这几乎就是真实商业项目的标准形态。从技术选型角度看这个项目天然适合Python生态。后端用Django还是Flask很多人纠结我的实际经验是如果追求快速出效果、后台管理直接可用Django是首选因为它的admin模块几乎开箱即用如果想更轻量、更灵活地控制每个接口Flask更顺手。这个项目里我最终采用了Django作为主后端Flask的写法也兼容保留了一部分这样两种框架的读者都能对照着看。前端则统一用Vue配合Vue Router做页面跳转Vuex或Pinia做状态管理整个前后端通过RESTful API通信。适合谁参考我认为有三类人最合适正在做毕业设计或课程设计需要一个完整度足够高的项目模板Python后端刚入门想搞明白Django和Flask在实际项目中分别怎么用前端只懂基础Vue语法想看看前后端分离的项目到底怎么组织。整个系统的完整流程可以这样理解游客在前端页面选择目的地 → 系统返回景点列表 → 游客选择景点下单 → 同时可以预订附近酒店 → 再通过航班模块查看往返班次 → 最终把所有预订记录汇总成一份旅行计划。这个过程听起来不算复杂但落到代码层面涉及用户认证、跨域请求、数据建模、订单状态机等一系列问题。2. 技术选型里藏着哪些决策逻辑Django、Flask、Vue和PyCharm的角色分工2.1 后端框架Django主战Flask为辅的原因先说Django。这个项目涉及景点、酒店、航班、订单、用户等多个数据实体彼此之间还有复杂的外键关系。Django的ORM层能让我用Python类直接描述这些关系省去了大量手写SQL的繁琐工作。举个例子一个订单需要同时关联用户、景点和酒店用Django模型写就是from django.db import models from django.contrib.auth.models import User class ScenicSpot(models.Model): name models.CharField(max_length100, verbose_name景点名称) city models.CharField(max_length50, verbose_name所在城市) ticket_price models.DecimalField(max_digits8, decimal_places2, verbose_name门票价格) description models.TextField(blankTrue, verbose_name景点介绍) def __str__(self): return self.name class Hotel(models.Model): name models.CharField(max_length100, verbose_name酒店名称) city models.CharField(max_length50, verbose_name所在城市) price_per_night models.DecimalField(max_digits8, decimal_places2, verbose_name每晚价格) available_rooms models.IntegerField(default0, verbose_name剩余房间数) class TravelOrder(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name下单用户) spot models.ForeignKey(ScenicSpot, on_deletemodels.SET_NULL, nullTrue, verbose_name关联景点) hotel models.ForeignKey(Hotel, on_deletemodels.SET_NULL, nullTrue, verbose_name关联酒店) travel_date models.DateField(verbose_name出行日期) status models.CharField(max_length20, defaultpending, verbose_name订单状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间)这段代码建好后运行python manage.py makemigrations和python manage.py migrate四张关联表就自动生成。Django的migration机制是我选择它的最大理由——它能记录每一次数据库结构变化团队协作或后续改动都能追根溯源。Flask在这个项目里的定位是“轻量补充”。比如说我可能需要一个独立的航班实时查询接口或者一个简单的数据导出功能这些如果也用Django那一套“app注册URL配置视图函数”流程代码量反而显得冗余。Flask可以用最小的代码提供一个HTTP接口from flask import Flask, jsonify app Flask(__name__) app.route(/api/flight/status, methods[GET]) def flight_status(): # 这里可以对接真实航班API也可以读数据库 data {departure: 北京, arrival: 上海, status: on_time} return jsonify(data)当然实际项目中不可能同时让Django和Flask监听同一个端口我的做法是让Django处理所有核心业务Flask作为独立的微服务处理非核心且访问量较小的接口两者通过HTTP相互调用。这样既保持了整体架构的一致性也演示了一个小型分布式系统的雏形。2.2 前端框架为什么是Vue而不是其他Vue的上手曲线在三大框架里最平缓这是公认的。对于后端起家的人Vue的模板语法非常友好v-for、v-if、v-model这些指令几乎不需要编译知识就能理解。更重要的是Vue生态中Vue Router和Pinia或Vuex这两个库正好覆盖了订票系统的两个核心需求——多页面跳转和全局状态共享。页面跳转很好理解主页、景点列表页、酒店详情页、航班查询页、我的订单页这些在Vue Router里就是一组路由配置// src/router/index.js import { createRouter, createWebHistory } from vue-router import Home from ../views/Home.vue import ScenicList from ../views/ScenicList.vue import HotelDetail from ../views/HotelDetail.vue import FlightSearch from ../views/FlightSearch.vue import MyOrders from ../views/MyOrders.vue import TripPlan from ../views/TripPlan.vue const routes [ { path: /, name: Home, component: Home }, { path: /scenic, name: ScenicList, component: ScenicList }, { path: /hotel/:id, name: HotelDetail, component: HotelDetail }, { path: /flight, name: FlightSearch, component: FlightSearch }, { path: /orders, name: MyOrders, component: MyOrders }, { path: /plan, name: TripPlan, component: TripPlan } ] const router createRouter({ history: createWebHistory(), routes }) export default router状态共享的需求更隐蔽但更重要。用户在景点页勾选了某个景点跳转到酒店页选择了酒店再跳到航班页选了航班最后生成旅行计划时需要把这三项数据汇总。如果每个页面都独立请求后端再临时组合产生冗余请求不说数据一致性也很难保证。用Pinia可以维护一个全局的“旅行意向”状态// src/stores/trip.js import { defineStore } from pinia export const useTripStore defineStore(trip, { state: () ({ selectedScenic: null, selectedHotel: null, selectedFlight: null, travelDate: }), actions: { setScenic(spot) { this.selectedScenic spot }, setHotel(hotel) { this.selectedHotel hotel }, setFlight(flight) { this.selectedFlight flight }, clearTrip() { this.selectedScenic null this.selectedHotel null this.selectedFlight null this.travelDate } } })这样无论用户在页面间怎么跳转最终生成旅行计划时数据都完好在store里。这个设计模式也是真实中大型前端项目的标准做法。2.3 开发工具PyCharm在这个项目里承担的角色PyCharm在这个项目里不只是代码编辑器。我用它做了几件对开发效率影响很大的事第一虚拟环境管理。PyCharm创建项目时会自动生成venv目录并且可以直接在Setting里指定解释器路径。这样一来Django版本、Flask版本、数据库驱动等依赖全部隔离在项目内部不会污染全局Python环境。第二Django的manage.py工具。PyCharm专业版提供了Django的专门支持可以直接在Run Configuration里设置启动参数点击绿色按钮就跑起开发服务器省去了每次在终端敲python manage.py runserver的麻烦。第三数据库的可视化操作。PyCharm左侧的Database面板可以直接连上SQLite或MySQL查看表结构、执行SQL、查看查询结果都很方便不需要额外打开Navicat或DataGrip。对于Django的ORM调试这个功能尤其有用——你可以清楚看到ORM生成的SQL到底是什么样子。社区版虽然在Django支持上弱一些但也足够用。关键的是把Python解释器、依赖安装、Git版本控制这三样配置好开发体验差距不大。3. 环境搭建与依赖安装最容易卡住新手的几个环节3.1 Python、Django框架与数据库的环境搭配很多人在环境这一步就放弃了其实根本原因是版本搭配出了问题。我推荐的组合是Python 3.10及以上 Django 4.2 LTS Vue 3 Pinia SQLite开发阶段。为什么Django选4.2 LTS因为Django的LTS版本会获得长期安全维护文档也最完善。如果你用了Django 5.0测试版某些第三方库可能还没适配遇到兼容性问题排查起来很耗时间。生产环境求稳LTS是常识。安装步骤非常简单在PyCharm的Terminal里依次执行# 创建并激活虚拟环境PyCharm新建项目时默认会做 python -m venv venv # Windows环境激活命令 venv\Scripts\activate # macOS/Linux环境激活命令 # source venv/bin/activate # 安装Django核心 pip install django4.2.* # 安装Django REST Framework用于构建前后端分离的API pip install djangorestframework # 如果要用MySQL安装驱动 pip install mysqlclient # 处理跨域请求 pip install django-cors-headers # 图片处理 pip install Pillow这里我要特别强调一个坑在Windows上安装mysqlclient经常失败因为需要编译环境。我的建议是开发阶段直接用SQLite功能完全够用等到部署生产环境再切MySQL。切换方式也简单修改settings.py里的DATABASES配置就行ORM代码完全不用动。顺便提一句安装完成后可以执行python manage.py check来验证Django项目配置是否正确。这个命令比直接runserver更轻量专门用来检查配置和依赖问题。3.2 Vue脚手架的安装与环境配置前端这块我建议直接使用Vite作为构建工具Vue官方的新项目默认也是Vite速度比旧的webpack快非常多。安装命令如下# 使用npm创建一个Vue 3项目 npm create vitelatest frontend -- --template vue # 进入目录并安装依赖 cd frontend npm install # 安装路由和状态管理 npm install vue-router4 pinia # 安装HTTP库用于请求后端API npm install axios这里要说一下为什么用axios而不是fetch。axios对请求拦截、响应拦截、错误处理封装得更完善尤其是需要在每个请求头里带上用户的token时统一在拦截器里设置比每个请求手动写要省事得多。后面我会给出具体的axios配置。另外Vue项目创建后会有一个默认的src目录结构我强烈建议按模块重新组织src/ api/ # 所有后端接口的请求封装 assets/ # 静态资源 components/ # 通用组件 router/ # 路由配置 stores/ # Pinia状态管理 views/ # 页面组件这种分层是Vue项目最常见的组织方式新手一定要从一开始就养成这样分类的习惯不然组件和页面一多文件目录会乱成一团。3.3 启动顺序与常见报错速查表整个系统要跑起来启动顺序我很明确先启动后端Django服务再启动前端Vue开发服务器。因为前端页面启动时需要去请求后端的API如果后端没起来页面会白屏或报接口错误。启动命令分别是在项目根目录执行# 终端1启动Django后端默认端口8000 python manage.py runserver 0.0.0.0:8000 # 终端2启动Vue前端开发服务器默认端口5173 npm run dev如果你用的是PyCharm可以把两套启动命令配置到Run Configuration里甚至用Compound配置一键同时启动两个服务。以下是新手最容易遇到的几个问题及解决办法都是我实测过的问题现象原因解决办法前端页面访问时接口404前后端跨域未配置在Django设置中添加django-cors-headers中间件和cors配置访问接口返回403 CSRF验证失败Django的CSRF防护机制在axios配置中获取CSRF cookie并设置到请求头pip install mysqlclient报错Windows缺少编译环境改用SQLite数据库或下载对应版本whl文件安装Vue页面启动时端口冲突5173端口被占用在vite.config.js中修改server.port5174图片存储在media目录但不显示未配置媒体文件的URL映射在urls.py中添加static和media文件服务路由4. 数据库建模与业务逻辑景点、酒店、航班、订单、旅行计划这五张核心表怎么设计4.1 数据模型的关系拆解订票系统的业务核心是“订单”而不是任何一个独立的实体。景点、酒店、航班都只是订单的附属对象。这一点想清楚了数据模型就不会乱。我把数据表设计成以下几张User用户表直接用Django自带的auth.User不重复造轮子ScenicSpot景点表包含名称、城市、门票价格、介绍、图片等TicketOrder景点门票订单表关联用户和景点记录游玩日期和购买数量Hotel酒店表包含名称、城市、每晚价格、剩余房间数、评分等HotelOrder酒店订单表关联用户和酒店记录入住和退房日期Flight航班表包含航班号、起降城市、起降时间、票价、余票量FlightOrder航班订单表关联用户和航班TripPlan旅行计划表把同一用户在某次旅行中关联的多个订单聚合在一起。这里要注意一个设计细节旅行计划表和订单表之间不是简单的“一对多”而是“多对多”的聚合关系。也就是说一个旅行计划可能包含景点订单、酒店订单和航班订单而用户也可以在不同的日期创建多个旅行计划。用Django的模型定义旅行计划可以这样写class TripPlan(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nametrip_plans) title models.CharField(max_length100, verbose_name计划名称) start_date models.DateField(verbose_name开始日期) end_date models.DateField(verbose_name结束日期) spots models.ManyToManyField(ScenicSpot, blankTrue, verbose_name包含景点) hotels models.ManyToManyField(Hotel, blankTrue, verbose_name包含酒店) flights models.ManyToManyField(Flight, blankTrue, verbose_name包含航班) created_at models.DateTimeField(auto_now_addTrue)用多对多字段Django会自动生成中间关联表包含了计划ID和景点ID、酒店ID、航班ID的对应关系。查询某个计划下的所有景点只需要plan.spots.all()一行代码。4.2 订单状态机的设计与实现订单状态是订票系统最容易做砸的地方。我见过很多新手用随机字符串表示订单状态比如0、1、2然后写一堆if判断逻辑。这种方式不但可读性差后续扩展状态的时候还容易出bug。我的做法是定义一组常量并在模型中设置choicesclass TravelOrder(models.Model): STATUS_PENDING pending STATUS_PAID paid STATUS_CANCELLED cancelled STATUS_COMPLETED completed STATUS_CHOICES [ (STATUS_PENDING, 待支付), (STATUS_PAID, 已支付), (STATUS_CANCELLED, 已取消), (STATUS_COMPLETED, 已完成), ] status models.CharField( max_length20, choicesSTATUS_CHOICES, defaultSTATUS_PENDING, verbose_name订单状态 )这样设计的好处是Django的admin后台会自动生成下拉选择框查数据库时也能一目了然看出状态含义。更进阶的做法是在模型或者视图层封装“状态转移”方法不允许随意改状态。比如只有待支付状态才能变为已支付已支付或待支付才能变为已取消def transition_status(self, new_status): allowed_transitions { self.STATUS_PENDING: [self.STATUS_PAID, self.STATUS_CANCELLED], self.STATUS_PAID: [self.STATUS_COMPLETED, self.STATUS_CANCELLED], self.STATUS_CANCELLED: [], self.STATUS_COMPLETED: [], } if new_status not in allowed_transitions.get(self.status, []): raise ValueError(f非法状态转换{self.status} - {new_status}) self.status new_status self.save()这套状态机逻辑虽然简单但能避免很多数据脏问题。比如用户已支付后又退款这种多步操作至少要保证订单状态和支付记录的对应关系不出错。4.3 库存与余票的并发处理航班余票、酒店剩余房间这些数据都存在并发访问的隐患。如果两个用户同时看到余票为1同时下单最后一定会超卖。Django的ORM默认情况下读取数据后在Python层面做判断再保存存在典型的“竞态条件”问题。解决办法是使用数据库的原子更新操作from django.db.models import F # 下单时对航班余票做原子减1操作 remaining Flight.objects.filter(idflight_id, remaining_seats__gte1).update( remaining_seatsF(remaining_seats) - 1 ) if remaining 0: raise ValueError(航班余票不足)这段代码的关键在于filter(remaining_seats__gte1)和update(F(remaining_seats) - 1)合在一起执行。数据库会在执行update时再次检查余票是否大于等于1从根本上避免了超卖问题。同样酒店的房间数量也可以用F表达式来做原子更新。这是真实项目中必须具备的并发处理意识课程设计很多不会讲但你面试时被问到的概率极高。5. API接口设计与前后端交互核心接口拆解与Axios封装实战5.1 RESTful API规划思路前后端分离项目最忌讳的是“一个页面一个接口”的零散规划。正确的做法是先梳理业务场景再按照资源维度设计统一的接口。以本系统为例我规划了以下核心接口组分组方法路径功能说明用户POST/api/auth/register/用户注册用户POST/api/auth/login/用户登录返回token用户GET/api/auth/profile/获取当前用户信息景点GET/api/scenic/list/景点列表支持关键词搜索景点GET/api/scenic/detail/id/景点详情酒店GET/api/hotel/list/酒店列表支持城市筛选航班GET/api/flight/search/航班查询按日期和城市订单POST/api/order/create/创建订单订单GET/api/order/list/我的订单列表旅行计划POST/api/plan/create/生成旅行计划旅行计划GET/api/plan/detail/id/旅行计划详情每个接口的返回格式也要统一方便前端处理。我建议的通用格式是{ code: 0, message: success, data: {...} }这样前端axios拦截器只需要判断code是否为0就可以决定走成功流程还是错误流程十分清爽。5.2 Django REST Framework的视图实现Django REST FrameworkDRF是构建API的神器。你可以用ModelViewSet几行代码就把列表、详情、增删改查全部实现也可以写APIView精细控制每个方法。对于订票系统的核心场景我用APIView多一点因为业务逻辑复杂不太适合完全靠序列化器自动生成。一个景点列表接口的典型实现如下from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import ScenicSpot from .serializers import ScenicSpotSerializer class ScenicSpotListView(APIView): def get(self, request): keyword request.query_params.get(keyword, ) city request.query_params.get(city, ) spots ScenicSpot.objects.all() if keyword: spots spots.filter(name__icontainskeyword) if city: spots spots.filter(citycity) serializer ScenicSpotSerializer(spots, manyTrue) return Response({ code: 0, message: success, data: serializer.data })再用序列化器控制从模型到JSON的转换字段from rest_framework import serializers from .models import ScenicSpot class ScenicSpotSerializer(serializers.ModelSerializer): class Meta: model ScenicSpot fields [id, name, city, ticket_price, description, image]创建订单这种写操作需要对用户身份做校验。DRF提供了IsAuthenticated权限类直接在类属性里声明即可from rest_framework.permissions import IsAuthenticated class OrderCreateView(APIView): permission_classes [IsAuthenticated] def post(self, request): user request.user # 业务逻辑... return Response({code: 0, message: 订单创建成功})5.3 前端Axios封装与统一鉴权处理前端的Axios封装是整个前后端交互体验的基石。我的习惯是创建一个api/request.js文件统一创建axios实例配置基础URL、拦截器、错误处理。// src/api/request.js import axios from axios import router from ../router const request axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000 }) // 请求拦截器自动携带token request.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token ${token} } return config }, error Promise.reject(error) ) // 响应拦截器统一处理业务错误和登录失效 request.interceptors.response.use( response { const res response.data if (res.code ! 0) { // 业务错误统一提示 console.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { // 登录过期清除本地登录态跳转登录页 localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } ) export default request这样封装完之后每个具体的接口文件只需要写业务逻辑不需要重复处理token和错误// src/api/scenic.js import request from ./request export function getScenicList(params) { return request({ url: /scenic/list/, method: get, params }) } export function getScenicDetail(id) { return request({ url: /scenic/detail/${id}/, method: get }) }页面组件里直接调用函数代码非常干净。这套封装逻辑放到任何前后端分离项目中都是通用的。6. 关键功能模块的开发要点搜索、下单、旅行计划生成的完整链路6.1 景点与酒店的多条件检索搜索功能听起来简单但要做到“条件叠加且逻辑正确”需要一点设计。以景点搜索为例用户可能同时选择城市、输入关键词、过滤价格范围。在Django视图层我用多个if判断来逐层过滤而不是写一条拼接SQLfrom django.db.models import Q def get_filtered_spots(request): spots ScenicSpot.objects.all() city request.query_params.get(city, ) keyword request.query_params.get(keyword, ) min_price request.query_params.get(min_price, ) max_price request.query_params.get(max_price, ) if city: spots spots.filter(citycity) if keyword: # Q对象支持多个字段的模糊匹配 spots spots.filter( Q(name__icontainskeyword) | Q(description__icontainskeyword) ) if min_price: spots spots.filter(ticket_price__gtemin_price) if max_price: spots spots.filter(ticket_price__ltemax_price) return spots这种写法的好处是每一个条件都是独立的后续想增加新的筛选维度只需要加一个if块代码可维护性极高。前端对应的Vue组件绑定搜索表单的数据点击搜索时重新调用API即可。6.2 订单创建中事务与数据一致性创建订单是一个多表写入操作。以一次酒店预订为例需要做的事有创建酒店订单记录、扣减酒店房间数、给用户发送通知。如果创建订单成功但扣减房间数失败就会出现脏数据。Django的transaction.atomic()可以保证这几步操作要么全部成功要么全部回滚from django.db import transaction from django.utils import timezone class HotelOrderCreateView(APIView): permission_classes [IsAuthenticated] def post(self, request): user request.user hotel_id request.data.get(hotel_id) check_in request.data.get(check_in) check_out request.data.get(check_out) with transaction.atomic(): hotel Hotel.objects.select_for_update().get(idhotel_id) if hotel.available_rooms 1: return Response({ code: 1, message: 酒店房间已满 }) # 扣减库存 hotel.available_rooms - 1 hotel.save() # 创建订单 order HotelOrder.objects.create( useruser, hotelhotel, check_incheck_in, check_outcheck_out, statuspending ) return Response({ code: 0, message: 订单创建成功, data: {order_id: order.id} })注意这里用了select_for_update()它会对选中的行加锁直到事务结束才释放。在高并发环境下这个锁能有效避免两个请求同时读到同一个剩余房间数。6.3 旅行计划的自动生成逻辑旅行计划是整个系统最出彩的功能。它不是一个独立下单流程而是把用户已创建的订单按日期和目的地汇总。我的实现方式是前端提供一个“生成计划”的按钮调用后端接口后端根据当前用户的景点订单、酒店订单和航班订单按日期排序组合成一份旅行计划草案返回给前端确认。核心逻辑如下class TripPlanCreateView(APIView): permission_classes [IsAuthenticated] def post(self, request): user request.user title request.data.get(title, 我的旅行计划) # 查找用户所有已支付订单 spot_orders TicketOrder.objects.filter( useruser, statuspaid ).select_related(spot) hotel_orders HotelOrder.objects.filter( useruser, statuspaid ).select_related(hotel) flight_orders FlightOrder.objects.filter( useruser, statuspaid ).select_related(flight) # 按日期排序生成计划内容 plan_items [] for order in spot_orders: plan_items.append({ type: scenic, name: order.spot.name, date: order.travel_date, address: order.spot.city }) for order in hotel_orders: plan_items.append({ type: hotel, name: order.hotel.name, date: order.check_in, address: order.hotel.city }) for order in flight_orders: plan_items.append({ type: flight, name: f{order.flight.flight_no} {order.flight.departure_city}→{order.flight.arrival_city}, date: order.flight_date, address: order.flight.departure_city }) plan_items.sort(keylambda x: x[date]) # 创建计划对象并保存多对多关系 plan TripPlan.objects.create( useruser, titletitle, start_dateplan_items[0][date] if plan_items else None, end_dateplan_items[-1][date] if plan_items else None ) for order in spot_orders: plan.spots.add(order.spot) for order in hotel_orders: plan.hotels.add(order.hotel) for order in flight_orders: plan.flights.add(order.flight) return Response({ code: 0, message: 旅行计划生成成功, data: { plan_id: plan.id, items: plan_items } })这个接口把前后端分离的价值体现得淋漓尽致——后端负责数据聚合和业务规则前端专注展示和交互。前端拿到items数组后按时间轴渲染就是一份可读性非常好的旅行计划。7. 后台管理系统与数据库可视化Django Admin的定制和PyCharm数据库面板的使用7.1 Django Admin的个性化配置Django自带的Admin后台对开发者来说是一份免费的礼物。只要完成模型注册表格展示、增删改查、分页、搜索这些功能全部自动生成。但默认界面比较朴素我们可以做一些简单的定制让它更适合直接给运营人员用。在admin.py中from django.contrib import admin from .models import ScenicSpot, Hotel, Flight, TravelOrder, TripPlan admin.register(ScenicSpot) class ScenicSpotAdmin(admin.ModelAdmin): list_display (name, city, ticket_price, created_at) list_filter (city,) search_fields (name, city) ordering (-created_at,) admin.register(TravelOrder) class TravelOrderAdmin(admin.ModelAdmin): list_display (id, user, status, travel_date, created_at) list_filter (status,) search_fields (user__username,)这里的search_fields甚至支持跨表查询比如user__username表示搜索用户表中的用户名字段。list_filter可以直接生成侧栏筛选器按城市、按订单状态筛选都是一键的事。我常常建议如果只是做内部演示或者给后台运营团队用Django Admin完全够了不需要再开发一套后台管理系统。真正需要自定义后台界面的时候再考虑用Vue单独写一套admin前端。7.2 PyCharm的Database面板实操技巧PyCharm的Database工具在调试ORM问题时非常顺手。点击右侧Database面板添加数据源选择SQLite找到db.sqlite3文件就能看到所有表结构。几个实用功能值得强调直接右键表选择“Jump to Query Console”可以手写SQL验证查询结果表格数据支持内联编辑改一个字段保存后立即生效在终端执行Django shell时python manage.py shell里可以配合ORM做数据Debug比写API测试更直接。有一个比较隐蔽的坑Django的migration会把表名变为“app名_模型名”的形式比如travel_app_scenicspot。新手在数据库面板里找表时可能因为表名对不上而蒙圈知道这个规则就没问题了。8. 部署与常见坑点从本地开发到可分享的完整项目8.1 前后端构建与静态文件处理项目开发完成后如果要部署到云服务器或者给其他人看演示需要分别处理前后端的构建产物。前端部分执行npm run buildVite会在 dist 目录下生成打包后的静态文件包括HTML、CSS、JS所有资源经过压缩合并。这些文件可以用Nginx直接托管也可以放到Django的static目录下。后端部分要把Django的DEBUG改为False配置ALLOWED_HOSTS为你的域名或IP同时收集静态文件# settings.py DEBUG False ALLOWED_HOSTS [your-domain.com, 127.0.0.1] STATIC_ROOT BASE_DIR / staticfiles执行python manage.py collectstatic这一步会把所有依赖的静态文件复制到STATIC_ROOT目录便于Nginx直接托管。8.2 SQLite到MySQL的切换迁移项目初期用SQLite没毛病但部署上线时我建议切到MySQL毕竟SQLite对并发写入的支持有限。切换步骤很简单DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }然后执行迁移命令python manage.py makemigrations python manage.py migrate由于Django ORM的抽象层做得好业务代码几乎不用动。唯一要提前检查的是某些字段类型在MySQL下的表现比如CharField长度不足可能导致索引超过上限。提前在本地搭一个MySQL测试环境迁移一遍能避免上线时的尴尬。8.3 新手最容易踩的坑汇总这么多期带新手的经验我把订票系统里高频出现的问题整理成一张清单CORS跨域没配置前端请求一定失败Django和Vue必须都配置好前端请求后端API时地址写错localhost和127.0.0.1有时会被浏览器视为不同源图片上传后页面无法显示检查Django的MEDIA_URL和urls.py中的media路由项目时间紧就放弃Flask部分只保留Django功能完整性优先Vue路由打包后页面404需要Nginx配置try_files把所有请求指向index.html记住每次修改模型后都要执行makemigrations和migrate否则表结构不对接口报错。9. 项目扩展方向我想继续做下去的几件事系统核心功能跑通之后还有几个值得探索的扩展方向第一个方向是接入真实数据。景点、航班信息可以对接公开的旅游数据API让系统从“演示数据”变成“可用数据”。这会涉及定时任务、数据缓存、API鉴权等功能技术深度会提升一大截。第二个方向是支付流程的模拟。目前订单只是停留在“待支付”状态如果在系统中接入一个模拟支付接口比如支付宝沙箱或微信支付沙箱从下单到支付回调再到修改订单状态形成闭环这个项目的完整度会跃升一个档次。第三个方向是推荐系统。根据用户的历史订单分析偏好在首页推荐相似的景点和酒店。用简单的协同过滤算法就能实现不需要引入复杂的机器学习框架。第四个方向是移动端适配。Vue 3生态里的Vant或NutUI组件库可以快速打造一套移动端H5页面让用户真正能在手机上完成整个订票流程。从个人项目经验来说这个订票系统的价值不只在“能运行”更在于它覆盖了一套完整业务系统的前后端全部环节。顺着它的业务链路把每一个模块吃透你对Django、Vue、数据库设计、前后端分离架构的理解都会比看十篇教程更深刻。如果正在做类似项目遇到具体问题随时可以对照上面的步骤排查。
返回列表