ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Vue+Django的网上药房商城:前后端分离实战解析

基于Vue+Django的网上药房商城:前后端分离实战解析 1. 项目整体拆解与方案选型先聊结论这个基于Vue Django/Flask的网上药房商城项目核心不是买药这个业务本身而是前后端分离架构的完整落地能力。我见过很多人把精力花在纠结到底用Django还是Flask结果项目拖了一个月还没搭出个能跑的骨架。这个项目最值得做的思路是业务上做出药品商城的特色处方药审核、库存效期管理、药品分类技术上跑通Vue前端 后端API 数据库的全链路这才是面试和工作中的硬通货。1.1 为什么前后端分离是这个项目的正解传统Django的模板渲染方式服务端直接输出HTML不是不能做商城但如果你打算让这个项目成为作品集里拿得出手的东西前后端分离几乎是必须的。原因很简单药品商城的信息架构比普通电商复杂——药品有通用名、商品名、剂型、规格、生产厂家、批准文号、库存批次、效期日期这些字段如果用模板渲染前端页面会非常臃肿后端每个页面都要写一堆视图函数后期维护成本高到你想删库跑路。Vue负责前端交互层Django/Flask只提供JSON接口这种模式的优势体现在三个地方开发并行前端写页面、后端写接口互不阻塞只要提前约定好接口文档我习惯用Swagger/DRF的Schema自动生成两边各干各的。接口复用药房商城以后很可能要出小程序端或App端后端接口是同一套不用重写。我见过太多人把逻辑塞进模板里加一个客户端等于重新做一遍系统。职责清晰药品库存计算、订单金额校验这些核心逻辑放在后端Vue只负责渲染数据和收集用户操作出错时排查范围小很多。1.2 Django还是Flask这次我两边都说清楚这个项目标题里同时出现了Django和Flask说句实话网上那些Flask比Django轻量、Django比Flask重型的说法对做商城系统的人来说参考价值不大。关键看你想要什么。选Django的场景项目需要自带的Admin后台直接管理药品数据、用户、订单需要ORM帮你省掉大量SQL编写需要内置的认证系统AuthUser、权限分组需要DRFDjango REST Framework快速生成带分页、过滤、认证的API。药房商城恰好全部命中——药品录入、订单管理、用户角色权限这些东西从零写会累死人Django的脚手架能让你把精力放在业务逻辑上。选Flask的场景你想要完全的掌控感项目结构自己定数据库用SQLAlchemy还是原生SQL自己选API用RESTful还是其他风格自己定。如果你已经有明确的设计思路不想被Django的应用app模式约束Flask上手更快Debug也更直观。我的建议很明确打算快速出成果选Django打算深入学习框架设计原理选Flask。这篇博文的技术细节主要围绕Django展开但我会把Flask对应的方案也点出来方便你按自己的路线走。提示如果你选FlaskSQLAlchemy Flask-RESTful JWT扩展基本能对标Django的ORM DRF Token认证但很多东西要自己组装前期节奏会慢一些。1.3 系统功能拆解药品商城不只是电商药品两个字把标题翻译成功能清单这个项目至少包含以下模块我直接按优先级排序模块核心功能优先级用户模块注册登录、收货地址管理、用户角色普通用户/管理员P0药品模块药品分类、药品详情、药品搜索、库存效期展示P0购物车模块加入购物车、修改数量、总价计算P0订单模块订单创建、订单状态流转待付款/待发货/待收货/已完成/已取消P0后台管理药品增删改查、订单处理、用户管理P1扩展功能药品真伪查询批准文号、用药禁忌提示、电子处方上传P2P0是骨架没有这些项目跑不起来P1是作品集的亮点P2是加分项如果你时间紧张可以先不做。实际开发中我习惯先把P0全部做完再碰P1/P2比如用药禁忌提示其实就是在药品详情表里加几个字段但前置工作是先把药品详情页做扎实否则提示放哪儿都别扭。2. 核心细节解析与实操要点这一章我会把项目里最关键的几个技术深水区拆开讲。这些点你在B站教程里大概率只能看到照着敲的结果看不到为什么这么设计。2.1 药品数据模型设计三个容易被忽略的坑药品表的设计是这类项目第一个深水区。大部分教程会让你建个简单的Drug表字段就是名称、价格、库存、图片然后就没了。真这么干后面做订单、做搜索、做后台管理的时候一定会返工。我建议的最小可用药品表字段如下用Django的model写法示意class Drug(models.Model): # 药品通用名比如布洛芬片 generic_name models.CharField(max_length128, db_indexTrue) # 商品名比如芬必得允许为空 brand_name models.CharField(max_length128, blankTrue, nullTrue) # 剂型片剂/胶囊/口服液/注射剂... dosage_form models.CharField(max_length50) # 规格比如0.3g*24片 specification models.CharField(max_length100) # 生产厂家 manufacturer models.CharField(max_length255) # 批准文号国药准字H开头的化学药、Z开头的中成药 approval_number models.CharField(max_length50, uniqueTrue) # 处方类型0-OTC非处方、1-Rx处方药 prescription_type models.IntegerField(default0, choices[(0, OTC), (1, Rx)]) # 零售价用DecimalField而不是FloatField price models.DecimalField(max_digits10, decimal_places2) # 库存数量 stock models.IntegerField(default0) # 有效期至这个字段比库存还重要后面解释 expiry_date models.DateField(nullTrue, blankTrue) # 药品说明书/功效简述 description models.TextField(blankTrue) # 分类外键 category models.ForeignKey(Category, on_deletemodels.PROTECT, related_namedrugs) # 上架状态 is_active models.BooleanField(defaultTrue) created_at models.DateTimeField(auto_now_addTrue)接下来是三个我第一次做这类项目时踩过的坑每一个都直接导致过返工第一个坑价格字段用了FloatField。浮点数在二进制里是不精确的0.10.2不等于0.3。药品价格涉及金额计算必须用DecimalField否则订单总价会出现0.00001元的差额用户可能不骂你但财务和后台对账的人会疯。第二个坑库存只存一个总数。药品是有批号和效期的同一款药可能有两批进货一批效期到2025年6月一批到2025年12月。电商系统里先进先出是标配但如果你想控制项目复杂度至少要把expiry_date这个字段做上——哪怕你暂时不做批次管理也要能展示本批次效期至某年某月。第三个坑分类删除了药品也跟着没了。药品表关联分类表时我用on_deletemodels.CASCADE结果后台删除一个分类整个分类下的药品全没了当场心态崩了。正确选择是PROTECT分类下有药品时禁止删除分类或者SET_NULL配合nullTrue保留药品。这个细节在面试里讲出来比背十道八股文都加分。2.2 Vue前端架构路由、状态管理、组件划分Vue端建议直接用Vue CLI创建项目组件结构按页面组件 通用组件两层来分。页面组件放src/views下比如Home.vue、DrugList.vue、DrugDetail.vue、Cart.vue、OrderConfirm.vue、OrderList.vue、Login.vue、AdminDrugManage.vue通用组件放src/components下比如DrugCard.vue药品卡片、SearchBar.vue、Pagination.vue、QuantityInput.vue。路由设计上不需要把每个药品详情都写成独立路由动态路由传参就行// router/index.js const routes [ { path: /, name: Home, component: Home }, { path: /drugs, name: DrugList, component: DrugList }, { path: /drugs/:id, name: DrugDetail, component: DrugDetail }, { path: /cart, name: Cart, component: Cart }, { path: /order/confirm, name: OrderConfirm, component: OrderConfirm }, { path: /order/list, name: OrderList, component: OrderList }, { path: /login, name: Login, component: Login }, { path: /admin/drugs, name: AdminDrugManage, component: AdminDrugManage, meta: { requiresAuth: true, requiresAdmin: true } } ]注意订单确认页的路径用了/order/confirm而不是/confirm这是为了让订单相关的路由在url上有一个清晰的命名空间对维护和理解都有好处。状态管理用Vuex或Pinia都行但别把所有数据都塞进去。我见过有人把药品列表也放Vuex数据量大之后直接卡成PPT。原则是全局共享的状态用户信息、购物车数量放Vuex/Pinia页面私有的数据当前药品列表、当前订单详情就放在组件自己的data里。购物车的设计有个小技巧不需要把整个购物车对象都存后端可以先存localStorage在下单确认时才把购物车数据提交给后端这样用户体验流畅后端压力也小。但要注意药品库存是实时变化的用户把药放进购物车三天后再结算可能库存已经没了。所以下单接口在后端必须重新校验库存不能信任前端传过来的有货状态。2.3 购物车和订单前后端协同的完整闭环订单流程是整个系统里前后端交互最密集的部分我拆成时序走一遍你就清楚了用户在药品详情页点击加入购物车→ Vue组件把药品ID和数量POST到后端购物车接口或先存localStorage用户进入购物车页→ Vue向/api/cart/发GET请求拿到购物车商品列表和实时价格用户点击去结算→ 前端跳转订单确认页同时GET请求/api/order/preview/后端计算总价、校验库存、返回默认收货地址用户点击提交订单→ 前端POST/api/orders/后端在事务里创建订单主表和订单明细表扣减库存支付成功后→ 前端轮询或接收WebSocket通知订单状态从待付款变为待发货这里有一个必须单独说的事库存扣减必须和订单创建在同一个数据库事务里。Django里用transaction.atomic()包住或者在DRF的Serializer的create方法里写逻辑。否则订单建了但库存没扣超卖就是这么来的。我用过一个很蠢的写法订单和库存分两个接口分别调结果测试时两个人同时买最后一盒药都成功了货只有一盒用户投诉直接把我干沉默了。# 伪代码订单创建事务 from django.db import transaction transaction.atomic def create_order(user, cart_items, address_id): order Order.objects.create(useruser, addressaddress_id, total_amountcalculate_total(cart_items)) for item in cart_items: drug Drug.objects.select_for_update().get(iditem[drug_id]) if drug.stock item[quantity]: raise ValidationError(f药品 {drug.generic_name} 库存不足) drug.stock - item[quantity] drug.save() OrderItem.objects.create(orderorder, drugdrug, quantityitem[quantity], pricedrug.price) return orderselect_for_update()是行级锁高并发下防止两个人同时读到同一份库存。这个知识点放在简历里写着熟悉数据库事务与并发控制面试官眼前一亮。3. 实操过程与核心环节实现这一章是完整的实操记录我会沿着环境搭建 → 后端接口实现 → 前端页面联调的顺序走一遍每一步都给出可直接抄的命令和代码。3.1 环境准备PyCharm、虚拟环境、数据库先说PyCharm。用社区版免费就够做这个项目了但如果你想调试Django模板或者用专业版里的数据库工具可以装专业版。安装时记得勾选Python插件把解释器指到你要用的Python版本。我推荐用Python 3.10或3.11Django 4.x和DRF对这两个版本支持最好Python 3.8能跑但没必要给自己添堵。初始化项目我习惯这样做# 创建虚拟环境 python -m venv venv # 激活Windows venv\Scripts\activate # 激活macOS/Linux source venv/bin/activate # 安装依赖 pip install django djangorestframework django-cors-headers pillow pip install python-dotenv # 创建Django项目和应用 django-admin startproject pharmacy_backend cd pharmacy_backend python manage.py startapp drug python manage.py startapp order python manage.py startapp user我不用startproject一步到位把整个商城当一个大app写而是拆成多个app——drug管药品和分类order管订单和购物车user管用户和收货地址。Django的app不是用来做插件体系的花架子它的核心价值是模块隔离。你非要用一个app写几千行models也行但后期维护、定位问题时你会体会到什么叫痛苦。数据库方面开发环境直接用SQLite就够了不用装MySQL。Django默认配置就是SQLite零配置直接跑。但如果你电脑上正好有MySQL也建议用上毕竟生产环境大概率是MySQL早熟悉早安心。如果选MySQL记得PyCharm里装好驱动Django连接MySQL需要mysqlclient或用pymysql做兼容层这一步经常有人卡住。3.2 后端接口实现从models到views再到serializers后端接口开发的顺序是models数据表→ serializers数据序列化→ views业务逻辑/API视图→ urls路由这个顺序是逻辑依赖关系先定义数据长什么样再写怎么转换最后写怎么处理请求。以药品列表接口为例我给出完整实现# drug/models.py - 已在前文给出这里省略 # drug/serializers.py from rest_framework import serializers from .models import Drug class DrugListSerializer(serializers.ModelSerializer): category_name serializers.CharField(sourcecategory.name, read_onlyTrue) # 动态计算是否临期效期在6个月内 is_near_expiry serializers.SerializerMethodField() class Meta: model Drug fields [id, generic_name, brand_name, specification, manufacturer, price, stock, expiry_date, category_name, is_near_expiry, image_url] def get_is_near_expiry(self, obj): if obj.expiry_date: from datetime import date return (obj.expiry_date - date.today()).days 180 return False # drug/views.py from rest_framework import generics, filters from .models import Drug from .serializers import DrugListSerializer class DrugListView(generics.ListAPIView): queryset Drug.objects.filter(is_activeTrue).select_related(category).order_by(id) serializer_class DrugListSerializer filter_backends [filters.SearchFilter, filters.OrderingFilter] search_fields [generic_name, brand_name, category__name] ordering_fields [price, id]几点说明为什么用select_related(category)药品列表要显示分类名称如果不联表查询DRF序列化时每一条都会额外发一次SQL查分类N1查询问题就来了。数据量小感觉不出来数据量一大接口就明显变慢。这也是面试常问的ORM优化知识点。为什么用ListAPIView而不是APIView自己写逻辑generics.ListAPIView自带分页、过滤、排序的接入能力少写一堆代码。DRF的generic视图是给你用的不是让你背源码的。但也要理解它做了什么这样才能在需要魔改时不迷路——它的核心流程就是获取queryset → 应用过滤器 → 分页 → 交给serializer序列化 → 返回Response。filter_backends里SearchFilter做的是模糊搜索。search_fields里写了category__name这样用户搜索感冒就能搜到感冒分类下的所有药品这个跨表搜索能力是DRF内置的不用自己写LIKE语句。药品详情接口类似但有一点不同要处理药品不存在的情况。class DrugDetailView(generics.RetrieveAPIView): queryset Drug.objects.filter(is_activeTrue) serializer_class DrugListSerializerDRF的RetrieveAPIView默认会在对象不存在时返回404这个行为是Django Http404的封装实际测试时可以用浏览器直接访问/api/drugs/999/验证一下。3.3 订单接口和权限控制DRF的Token认证实际配置药品商城有普通用户和管理员两种角色接口权限必须控制。DRF内置的权限系统够用但要做两点配置启用Token认证或JWT以及给管理员接口加IsAdminUser权限。先在settings.py里配置# config/settings.py INSTALLED_APPS [ # ... rest_framework, rest_framework.authtoken, corsheaders, drug, order, user, ] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.TokenAuthentication, rest_framework.authentication.SessionAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticatedOrReadOnly, ] }IsAuthenticatedOrReadOnly表示未登录用户可以GET浏览药品但POST/PUT/DELETE需要登录。这个安全级别正好符合商城的形态——游客能逛、能搜但加购物车和下单必须登录。后台管理药品的接口需要管理员权限from rest_framework.permissions import IsAdminUser class AdminDrugCreateUpdateView(generics.CreateAPIView, generics.UpdateAPIView): queryset Drug.objects.all() serializer_class DrugAdminSerializer permission_classes [IsAdminUser]前端登录时后端返回TokenVue把Token存到localStorage之后每次请求在请求头带上Authorization: Token 这里填token。Django自带的TokenAuthentication已经实现了这个协议不需要自己造轮子。跨域配置也是必须的因为前端跑在http://localhost:8080后端跑在http://localhost:8000端口不同就是跨域。# 已安装django-cors-headers CORS_ALLOWED_ORIGINS [ http://localhost:8080, http://127.0.0.1:8080, ] CORS_ALLOW_CREDENTIALS True这个配置漏掉后前端请求接口会直接报CORS错误经常有人卡这关卡半天。3.4 Vue前端搭建从脚手架到首页药品列表Vue端实操步骤我完整写出来。前提是你电脑上已经装好了Node.js和npm。# 安装Vue CLI如果还没装 npm install -g vue/cli # 创建项目Vue 3 vue create pharmacy_frontend # 选择手动配置勾选Router、Vuex/Pinia、ESLint cd pharmacy_frontend npm install axios vue-router4 pinia把src目录结构调整成和之前说过的匹配然后先做一件事配置API请求的axios实例。这一步很重要后面所有接口请求都要通过它统一发出方便统一处理Token、统一拦截错误。// src/api/http.js import axios from axios const http axios.create({ baseURL: http://localhost:8000/api, timeout: 10000 }) // 请求拦截器自动带Token http.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token ${token} } return config }) // 响应拦截器401统一跳登录 http.interceptors.response.use( response response, error { if (error.response error.response.status 401) { localStorage.removeItem(token) localStorage.removeItem(userInfo) window.location.href /login } return Promise.reject(error) } ) export default http在Vue里这个http模块被Home页面调用时大致长这样局部截取template div SearchBar searchhandleSearch / div classgrid DrugCard v-fordrug in drugs :keydrug.id :drugdrug / /div Pagination :pagepage :totaltotal changeloadDrugs / /div /template script setup import { ref, onMounted } from vue import http from ../api/http import DrugCard from /components/DrugCard.vue const drugs ref([]) const page ref(1) const total ref(0) async function loadDrugs() { const response await http.get(/drugs/, { params: { page: page.value, search: searchKeyword.value } }) drugs.value response.data.results total.value response.data.count } onMounted(loadDrugs) /script注意response.data.results和response.data.count是DRF默认分页格式——如果没配置分页类返回的就是一个数组取数方式会不同。我建议在DRF里启用PageNumberPagination并把page_size设为12这样分页逻辑前后端就是一套了。3.5 路由守卫和动态路由权限控制的前端最后一环后端已经做了权限控制但前端也需要配合做路由守卫否则未登录用户直接访问后台管理页页面白屏之前会先看到一帧空白或报错的界面体验很差。// router/index.js 补充 router.beforeEach((to, from, next) { const token localStorage.getItem(token) const isAdmin localStorage.getItem(userRole) admin if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.requiresAdmin !isAdmin) { next(/) } else { next() } })这个守卫逻辑简单明了需要登录的页面没Token就踢到登录页需要管理员的页面角色不对就踢回首页。注意一点**localStorage里的userRole是前端存的理论上可以被篡改所以真正决定能不能执行管理员操作的是后端的IsAdminUser权限。**前端的守卫只是用户体验优化不是安全边界。这个认知很重要面试时把这个主动讲出来会显得你真的懂权责划分。4. 常见问题与排查技巧实录这章内容全部来自我实际开发这类项目的踩坑记录每一条都有人问过我。4.1 Django执行查询时怎么删除对象这是后台管理里最常见的操作。Django删除单条对象很简单drug Drug.objects.get(id1) drug.delete()但批量删除有个坑。Drug.objects.filter(category_id3).delete()会级联删除关联的外键对象如果外键是CASCADE模式。刚才在models设计时我强调了用PROTECT所以如果有订单项引用这个药品删除会报ProtectedError这就是我们想要的——不允许删掉有订单记录的药品只能把is_active设为False做下架处理。实践中删除药品应该要实现为逻辑删除下架而不是物理删除# 推荐下架 drug.is_active False drug.save()物理删除的后果是历史订单里关联的药品信息就没了以后运维查订单、财务对账时会骂娘。**所以我在写商城系统时有一个默认原则凡是被业务记录引用过的数据只做状态变更不做物理删除。**这一条放在你项目里写注释评审的人看了会点头。4.2 跨域报错CORS怎么排查前端Vue页面报Access-Control-Allow-Origin错误时大部分人第一反应是前端的问题其实是后端没配置好。检查顺序如下后端有没有装并配置django-cors-headers或Flask的flask-corssettings.py里CORS_ALLOWED_ORIGINS是否包含了前端地址前端请求是不是带了自定义Header比如Authorization如果是后端还需要设置CORS_ALLOW_HEADERS加上自定义头确认后端终端里有没有打印出请求记录——如果后端根本没收到请求说明不是CORS问题是前端代理配置问题我建议用Vue CLI的devServer配置代理代替直接跨域// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } }这样前端请求/api/drugs/相当于请求http://localhost:8000/api/drugs/而且是同源请求不走CORS问题直接少一半。axios的baseURL也可以改成/api部署时用Nginx统一转发这套方案从开发到上线都很顺。4.3 PyCharm里调试Django的关键技巧在PyCharm里调试Django项目不要每次都手动在终端跑python manage.py runserver。正确姿势是在PyCharm右上角选Add Configuration选择Django Server配置好项目目录、settings路径、Python解释器。然后你就能像调试普通Python程序一样在views.py里打断点请求接口时逐行看数据流。我遇到过新手在PyCharm里导入项目后一直提示Python解释器不对或没有Django环境绝大多数是虚拟环境没选对。在Settings → Project → Python Interpreter里选venv/Scripts/python.exeWindows或venv/bin/pythonmacOS/Linux选完过几秒PyCharm会自动索引所有已装的依赖。调试接口时我还有一个习惯先不加断点直接用浏览器或Postman访问接口看返回结果确认返回值正常再上断点查逻辑。这是因为很多问题压根不在Python代码里而在数据本身——比如某条药品数据没有填价格字段序列化后返回null前端显示成了NaN元这种问题优先检查脏数据而不是代码。4.4 Flask方案对应的关键差异如果你确定选Flask而不是Django下面这几个点必须单独处理ORM默认没有要单独装Flask-SQLAlchemy模型定义风格类似Django的models但字段写法稍有不同Admin后台没有现成的如果想快速获得管理后台用Flask-Admin插件但配置自由度低一些或者自己写管理页面正好把Vue的管理页做复杂一点认证用Flask-JWT-Extended或Flask-Login前者更符合前后端分离序列化用Flask-RESTful的marshal_with或直接用Flask-Smorest带OpenAPI文档比Django的DRF更轻但生态和文档量不如DRFFlask建表后记得在模型里配置db.create_all()或在Flask启动时调用否则表不会自动创建。这个跟Django的migrate不一样新手经常跑到接口时报no such table就是这个原因。4.5 库存与超卖的经典坑位排查再展开一下库存问题因为这属于商城系统的关键路径问题。测试方法很简单用两个浏览器普通窗口和隐身窗口同时登录两个账号同时下单最后一盒库存药品。如果两个订单都显示成功说明超卖了事务和行锁肯定有一个地方写错了。排查顺序下单接口有没有用transaction.atomic——没有就用上库存扣减前有没有select_for_update()锁行——没有就用上前端是不是在订单提交前也做了一次库存检查——这是一个友好提示但不能作为最终依据最终以后端校验为准数据库引擎是不是MyISAM——MyISAM不支持行级锁要用InnoDBSQLite默认支持事务但支持的锁粒度有限测试并发场景最好切到MySQL或PostgreSQL。我之前开发时就直接用了MySQL省得后期切换数据库时又踩一遍驱动、字符集、时区的坑。4.6 前端Vue常见报错速查表报错信息原因解决方式Failed to load tsconfig vue/tsconfig...TypeScript配置引用缺失如果是JS项目就别引入tsconfigTS项目则重装vue/tsconfig并配置extendsCannot find module ./App.vue路径拼写错误检查import路径大小写和相对路径层级[Vue warn]: Property X was accessed during render but is not defined变量还没在data里定义在setup或data中定义对应响应式属性Request failed with status code 404后端接口路径不存在先浏览器直接访问该URL确认接口是否通TypeError: Cannot read properties of undefined (reading id)接口返回的数据结构不符检查后端序列化字段和前端取值是否一致我在联调阶段最常用的一招**浏览器F12 → Network面板 → 点开某个请求 → 看Response和后端接口文档对比字段。**做前后端分离项目90%的问题都出在我以为后端返回了这个字段上。接口文档能解决大部分但真实开发中还是要亲自看一次返回数据心里才踏实。5. 项目打包上线和路径规划从一个作业变成一个作品集做完成功能跑起来的系统后离作品集项目还差半步。这半步是把项目从我的电脑能跑变成任何人克隆下来都能跑再变成部署到服务器上让人通过域名访问。很多人的简历写独立开发网上药房商城系统结果代码只存在他自己电脑里这说服力等于零。5.1 项目骨架和README的重要性把项目克隆到新电脑别人跑不起来问题几乎都出在依赖和环境。所以在项目根目录必须放一个清晰的README至少包含这些内容项目简介和功能列表技术栈说明Python版本、Django版本、Vue版本、Node版本安装步骤创建虚拟环境、pip install -r requirements.txt、npm install数据库初始化命令manage.py migrate、创建管理员账号的createsuperuser命令启动命令后端runserver、前端npm run serve测试账号如果准备了演示账号写清楚用户名和密码依赖列表要固定版本我生成requirements.txt后还会再手动改一遍把关键包名后的版本号固定死如Django4.2.7避免一年后Django升到5.x导致别人跑不起来。5.2 部署策略Nginx uWSGI/Gunicorn Vue静态文件部署不是必须做但做了之后项目的含金量会上一个台阶。最简单的部署方案后端用Gunicorn跑DjangoDjango项目用Gunicorn比uWSGI配置简单前端npm run build打出dist目录交给Nginx托管静态文件DB从SQLite切到MySQL或PostgreSQL部署时这个必须切SQLite扛不住并发启用HTTPS用Certbot申请免费证书部署过程中最常见的坑是静态文件和MEDIA路径设置。Vue打包后的JS/CSS是独立的Django Admin的静态文件是Django自带的设置了STATIC_ROOT后别忘了跑python manage.py collectstatic。药品图片上传路径要配MEDIA_ROOT和MEDIA_URLNginx里对应加上location映射。我在第一次部署时卡了快两天的一个问题是Nginx配置了前端/指向dist目录后端/api反代到Gunicorn但前端页面刷新后直接404因为Vue是SPA路由是前端管理的Nginx需要配置try_files $uri $uri/ /index.html;。这个细节不写清楚部署后你能正常访问首页但一刷新详情页就404非常迷惑。5.3 这个项目还能怎么延伸如果时间和精力允许以下几个方向可以让项目特色更突出用药提醒在订单完成后生成一个用药日程前端用定时提醒组件做推送后端只需存提醒计划不需要真做短信发送电子处方上传处方药购买时让用户上传处方照片后台管理端审核后订单才能继续流转这是药品商城和普通电商差异化的核心功能做出来非常有辨识度药品科普文章模块用Django的GenericRelation或者简单的新增一个article app定期发一些用药科普提升站点的内容权威感可视化统计用ECharts在管理后台展示销售Top10药品、月度营业额曲线这个对展示自己的全栈能力非常加分购物车实时库存提示结合WebSocket或者短轮询在购物车页提示库存变化算是一个前端进阶点这些延伸都是在P0功能完成的基础上做的。我见过太多人主功能没写完就开始堆各种花活最后项目看起来功能很多但每个功能都半残。我的经验是先把买药全流程走通——注册、搜索、加购物车、下单、后台看到订单并标记发货、用户看到订单状态更新这一条链路通了项目已经超过80%的竞品了。我个人在做这类项目时体会最深的一点是**技术选型永远是被业务目标驱动的。**你想展示的是我能独立做一套完整系统那就选Django这套省心方案把省下来的时间用来打磨功能完整度、代码规范和数据模型设计。你想展示的是我对框架底层有掌控力那就选Flask从零组装每一块功能这个过程本身就是最好的学习。两个方向都能做出好项目怕的是你来回摇摆一个月过去还在初始化阶段。最后再分享一个小技巧项目完成后给自己录一段3分钟的演示视频录屏幕的时候从启动后端服务开始演示注册登录、搜索药品、加购物车、下单、后台管理发货全过程。这个视频在你投简历、写作品集链接、面试前回顾时都有用等于给项目做了一个PPT式demo。做完这一步这个项目对你个人能力的加成就真的拉满了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进