做婴幼儿用品销售网站,是目前很多学Python的朋友喜欢拿来练手的一个项目,刚好能把Python、Vue、Pycharm、Django/Flask这一整套技术串起来。我之前带毕业设计的时候,遇到过不少同学问:后端到底用Django还是Flask?前端为什么要配Vue?从零开始怎么在Pycharm里面把一个前后端分离的商城系统跑起来?这篇文章我就从实际开发的角度,把婴幼儿用品销售网站的完整搭建过程、技术选型、核心代码和踩坑记录都整理出来,希望对正在做类似项目的你有帮助。我会尽量跳过废话,直接讲能落地的内容,所有步骤都是我实际跑过、调过、翻过车的经验。
1. 项目定位与技术选型
1.1 婴幼儿用品网站到底需要哪些功能
先别急着写代码,任何项目第一步都是把需求理清楚。婴幼儿用品销售网站虽然叫“商城”,但它的边界比普通电商更清晰。我梳理下来,核心模块一般就这几个:
- 用户端:注册、登录、退出、商品按分类浏览、关键词搜索、商品详情、购物车、下单、模拟支付、个人订单列表。
- 管理端(后台):商品分类管理、商品增删改查、库存管理、订单状态管理、用户管理。
如果做毕业设计,后台可以直接用Django原生Admin,省去不少工作量;如果想体现技术点,就自己写一套Vue管理页面。我的建议是:前端用Vue做用户端,后台先用Django Admin撑着,优先把商城的主流程跑通,后面还有时间再美化后台。
数据库设计也不复杂,核心表就六张:用户表、分类表、商品表、购物车表、订单表、订单明细表。婴幼儿用品有一个特殊点:商品信息里往往要标注适用月龄,比如“0-6个月”“6-12个月”,这个字段在设计商品表时一定要预留,后面做筛选会很好用。
1.2 Django和Flask怎么选更合适
很多人在Django和Flask之间纠结,其实这两者的选型逻辑非常简单:如果你想快速做出一个自带后台管理、ORM、认证体系的完整网站,选Django;如果你只想写几个JSON接口、想完全自己掌控代码结构,选Flask。放到婴幼儿用品销售网站这个场景里,我比较推荐Django,因为商城类项目天然需要用户管理、数据建模、后台维护,这些Django都内置了。
下面是两条路线的对比,都基于我实际用下来的感受:
| 对比项 | Django | Flask |
|---|---|---|
| ORM | 自带,功能完整,支持迁移 | 需要另装SQLAlchemy,配置多一点 |
| Admin后台 | 自带,改一改就能用 | 没有原生后台,需要自己写 |
| 用户认证 | 自带User模型和登录会话 | 需要Flask-Login |
| 序列化API | 配合DRF非常好用 | 手动返回JSON,或用Flask-RESTful |
| 学习曲线 | 稍陡,但解决的是完整套路 | 平缓,但到后期很多功能要自己拼 |
| 典型场景 | 内容管理、电商、内部系统 | 轻量服务、小工具、算法演示 |
如果你特别想用Flask,也没问题,后面的数据库设计和接口思路完全通用。只是注册登录、后台管理这些需要自己多写一些代码。其实这两套我都跑过,Flask写出来的代码结构更“自由”,但自由也意味着你要有足够的架构能力;Django则是“该做的都帮你做好了”,只要按它的规矩走,基本不踩大坑。
1.3 前端为什么选Vue而不是服务端模板
用Django也可以直接渲染HTML模板,但那样的话,商品列表每次刷新都要整个页面重新加载,体验比较老式。Vue的核心价值在于:它把前端变成了一个“单页应用”,用户点击切换分类时,只更新页面局部数据,不用刷新整页。这种交互方式对购物场景非常重要,用户加购物车、看商品详情、切换分类都会顺畅很多。
热词里总有“vue路由”“vue插槽”“vue指令简写”,说明大家在前端这层的问题比较集中。Vue的路由用来控制页面跳转,插槽用来复用组件结构,指令简写则是写着方便,比如 v-on 写成 @,v-bind 写成 :。这些内容我会在第3章结合页面实现一起讲。
用Vue做前端,一般走前后端分离:Vue负责页面和交互,Python负责提供JSON数据接口。这样分工清楚,后面部署时前端打包成静态文件,后端用Nginx反代,整体结构也很舒服。
2. 开发环境准备与项目初始化
2.1 Python、Pycharm、Node环境的安装要点
很多新手卡在环境这一步,其实学会看版本号就能解决大部分问题。我用的是Python 3.10,Pycharm 2024社区版,Node 18以上。Python和Pycharm的安装教程网上很多,我只提醒三个容易出问题的点:
- 安装Python时一定要勾选“Add Python to PATH”,否则后面在命令行敲
python会提示找不到命令。 - Pycharm创建新项目时,要选“New environment using Virtualenv”,让每个项目独立的包环境,避免和全局环境冲突。
- Node安装后要注意npm的镜像源,国内环境不配置镜像源的话,
npm install能卡到你怀疑人生。
配置npm镜像源只需要一行命令:
npm config set registry https://registry.npmmirror.comVue项目初始化我更喜欢用Vite,因为比老版的Vue CLI快很多。刚才提到的热词里有一个“vue安装依赖”,指的就是在项目根目录执行npm install,这一步会把项目需要的所有前端包都装进来。装完之后,用Pycharm打开后端项目,再用VS Code或Pycharm的“Open”打开前端文件夹,两个项目各自独立,但都在一个总目录下,方便管理。
2.2 创建Django后端项目并完成基础配置
后端项目我建议用命令行创建,Pycharm只是用来编辑和调试。先新建一个总目录,然后打开命令行:
pip install django djangorestframework django-cors-headers pillow django-admin startproject momshop cd momshop python manage.py startapp goods python manage.py startapp users python manage.py startapp orders我把功能拆成了三个app:goods管商品和分类,users管用户,orders管购物车和订单。Django的app概念可以理解成“功能模块”,它不是什么高深的东西,就是让同类代码放到同一个文件夹里。
创建完之后,把rest_framework、corsheaders和我们新建的users、goods、orders都注册到settings.py的INSTALLED_APPS里。同时配置数据库。练手阶段用SQLite最省事,因为不需要额外装数据库服务,但真正要部署上线建议换MySQL。配置项大概是这样的:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', # ... 其他默认中间件 ] CORS_ALLOW_ALL_ORIGINS = TrueCORS_ALLOW_ALL_ORIGINS是开发阶段用来解决跨域问题的临时手段,前后端分离时,前端跑在5173端口,后端跑在8000端口,端口不同浏览器就会拦截跨域请求,加上这个配置可以快速放行。上线前再改成白名单模式。
2.3 创建Vue前端项目并安装基础依赖
前端我用Vite创建一个Vue3项目,命令如下:
npm create vue@latest shopweb创建过程中会问你需不需要TypeScript、Router、Pinia等,我建议全部选“Yes”。TypeScript现在已经是主流,一开始可能不适应,但后面能帮你避免很多数据格式错误。Router是路由库,Pinia是状态管理库,商城项目一定用得上。
创建完成后,进入目录安装axios,用于发AJAX请求:
cd shopweb npm install npm install axios跑起来之后,Vite默认端口是5173,浏览器打开就能看到Vue雏形。这时候前后端两个项目都跑起来了:Django在8000,Vue在5173。接下来要做的就是把它们通过接口联通。
有一个热词是“failed to load tsconfig”,这个问题在Vite创建Vue项目时很容易出现,通常是因为@vue/tsconfig包没有正确安装。解决办法是删掉node_modules和package-lock.json,重新执行npm install。如果还报错,就把tsconfig.json里的extends路径改成相对路径,或者把该包单独执行一次npm install -D @vue/tsconfig。
3. 数据库设计与核心接口实现
3.1 数据表设计:从需求到字段
数据库设计是整个项目的地基,我见过不少项目写到一半开始改表,改到后面连带接口全乱。所以我习惯先用一张表把字段固定下来。
用户表可以直接继承Django的AbstractUser,加上手机号和收货地址:
from django.contrib.auth.models import AbstractUser class User(AbstractUser): phone = models.CharField(max_length=11, blank=True) address = models.CharField(max_length=255, blank=True) class Meta: db_table = 'user'分类表很简单,就是分类名称和排序权重。商品表是最核心的,除了基础字段,要特别注意stock(库存)和suitable_age(适用月龄)这两个字段:
class Category(models.Model): name = models.CharField(max_length=50) order = models.IntegerField(default=0) class Meta: db_table = 'category' class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') name = models.CharField(max_length=100) price = models.DecimalField(max_digits=8, decimal_places=2) stock = models.IntegerField() suitable_age = models.CharField(max_length=50, blank=True) image = models.ImageField(upload_to='products/', blank=True, null=True) description = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'product'购物车表和订单表是关联关系最强的。购物车每个条目关联一个用户和一个商品,订单关联用户且一对多到订单明细。需要注意的是:订单金额应该把总价也存进去,因为商品价格将来可能变动,订单历史金额不能被“联动”修改。这是电商系统一个很经典的冗余设计。
订单删除操作在Django里使用delete()方法,比如order.delete()。热词里那个“django执行查询-删除对象”实际就指模型实例调用delete(),或者用objects.filter(...).delete()做批量删除。这个操作是不可逆的,我在开发环境里吃过亏,后来养成了“删除前先打印查询集条数”的习惯。
3.2 后端接口设计与实现
我把接口风格统一成REST风格,用Django REST framework(DRF)来写。为什么要用DRF?因为它把JSON序列化、字段校验、路由注册都简化了,比手动写JsonResponse干净太多。
比如商品列表接口,只需要一个视图集:
from rest_framework import viewsets from .models import Product from .serializers import ProductSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all().order_by('-created_at') serializer_class = ProductSerializer然后在urls.py里注册:
from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('products', ProductViewSet) urlpatterns = [ path('api/', include(router.urls)), ]这样一个接口就能同时支持商品列表、商品详情、新增、修改、删除等多个操作,非常省事。DRF还自带了可视化调试页面,浏览器访问/api/products/可以直接看到数据,对于调试接口比手动请求方便得多。
用户注册登录稍微涉及一点Django认证机制。注册时不要直接明文存密码,Django自带create_user()会自动加密密码。登录我们采用JWT方式,整个流程是:前端提交用户名密码,后端验证成功后签一个token返回给前端,前端保存token,之后每次请求都在Header里带上Authorization: Bearer 你的token。
代码示例:
from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from django.contrib.auth import authenticate from rest_framework_simplejwt.tokens import RefreshToken @api_view(['POST']) def login(request): username = request.data.get('username') password = request.data.get('password') user = authenticate(username=username, password=password) if user is not None: refresh = RefreshToken.for_user(user) return Response({ 'access': str(refresh.access_token), 'refresh': str(refresh), }) return Response({'detail': '密码错误'}, status=status.HTTP_400_BAD_REQUEST)如果你走Flask路线,登录认证同样可以用JWT,只是框架不同,逻辑完全一致。Flask加SQLAlchemy的模型写法虽然和Django不同,但数据表设计和接口设计思路可以直接复用。
3.3 Vue页面与接口对接
前端这边,我需要做的页面包括:首页商品列表、商品详情页、分类筛选、购物车、结算页、登录注册页。Vue Router负责这些页面之间的跳转,比如首页点商品卡片跳转到详情页,配置方式如下:
const routes = [ { path: '/', component: Home }, { path: '/product/:id', component: ProductDetail }, { path: '/cart', component: Cart }, { path: '/login', component: Login }, ]跳转时用带参路由,在组件里通过route.params.id拿到商品ID,再调接口拿商品详情。
axios请求我们可以抽出一个公共封装,把baseURL、token、错误处理都集中起来:
import axios from 'axios' const http = axios.create({ baseURL: 'http://localhost:8000/api', timeout: 5000 }) http.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })商品列表页最核心的展示逻辑就三个:加载数据、渲染数据、条件筛选。加载数据在onMounted里调用接口,数据放到ref里驱动页面渲染。分类筛选用路由query或选中项,每次变化重新请求对应分类的商品。
Vue插槽这个热词,在商品卡片组件里很实用。比如商品卡片都有“图片区”“信息区”“操作按钮区”,不同页面可能对操作区要求不一样,此时就可以用插槽让父组件自定义按钮内容。而Vue的指令简写也能让代码更简洁:@click="goDetail"等同于v-on:click="goDetail",:src="item.image"等同于v-bind:src="item.image"。写习惯之后会发现,很多人用插槽和指令简写只是为了减少代码量,但实际上这两者真正的价值是提高了组件复用性。
购物车是前后端分离项目里最需要设计的一块。我的方案是分两种状态:未登录时,购物车存在浏览器localStorage里,用户选商品、改数量都直接改本地数据;登录后,购物车同步到后端。用户没登录也能逛、能加购物车,这个体验很关键,很多新手只做“必须登录才能加购”的版本,转化率其实很差。
4. 前后端联调、图片上传与部署
4.1 跨域与代理配置
跨域是前后端分离绕不开的第一个坑。前面我在Django里设置了CORS_ALLOW_ALL_ORIGINS = True,那是后端允许跨域的方式。但更推荐的前端做法是配置代理:让Vite在开发环境把/api开头的请求转发到后端的8000端口。这样前端代码里的baseURL可以写/api,浏览器看到的请求是同源的,就不存在跨域问题了。
在Vite项目的vite.config.js中加入:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } })设置完成后,前端调用/api/products/,Vite会把请求转发到http://localhost:8000/api/products/。这个配置能让你在开发阶段少掉80%的跨域烦恼。后端那边我会同时保留cors-headers配置,因为生产环境前后端可能部署在不同域名,代理不一定管用。
4.2 商品图片上传与访问路径
商品图片是一个非常影响体验的模块。Django中图片上传,我们需要设置MEDIA_ROOT和MEDIA_URL:
MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'开发模式下,在urls.py中追加静态路由:
from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)这样,上传到media/products/目录下的图片就能通过http://localhost:8000/media/products/xxx.jpg访问了。前端页面里直接把这个URL拼到<img>标签的src上即可。
但这里有个必须注意的点:如果Nginx没有配置/media/路径,上传后图片会404。所以生产环境一定要把media目录映射到Nginx的静态文件路径里。
4.3 部署上线:从Pycharm到云服务器
部署部分,我以Django为例讲完整流程,Flask部署的Nginx配置差不多,只是启动命令不同。
本地开发完成后,在云服务器上下载代码,安装依赖,然后执行数据库迁移:
python manage.py collectstatic python manage.py migrate用Gunicorn启动Django应用:
gunicorn momshop.wsgi:application -b 0.0.0.0:8000前端项目先执行npm run build,生成了一个dist静态文件目录。然后配置Nginx,把用户访问的80端口指向dist目录,同时把/api开头的请求反向代理到8000端口,把/media开头的请求指向Django的media目录。核心配置如下:
server { listen 80; server_name example.com; root /path/to/shopweb/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /media/ { alias /path/to/momshop/media/; } }配置完成后统一用Supervisor或systemd管理Gunicorn进程。千万别用python manage.py runserver跑生产环境,那个自带的开发服务器性能极差,是专门给本地调试用的。
5. 常见问题与排错速查
5.1 Django与Vue联调经典报错
前后端联调时,最常见的报错有两种:一种是403 Forbidden,另一种是404 Not Found。
403一般是CSRF验证没过。Django默认对POST请求做CSRF验证,而JWT登录方式是发POST请求,如果不加@csrf_exempt,就会报403。我在给登录视图加装饰器时踩过这个坑,后来统一在JWT认证中间件中忽略CSRF。
404就复杂一些,要区分是路由没匹配还是请求路径拼错。建议用浏览器开发者工具里的Network面板,看实际请求的URL到底是什么。很多404是前端baseURL写错了,比如多了一个斜杠,或者漏了/api。
5.2 数据库相关坑
数据库最常见的坑:迁移不同步。改完模型后忘了执行python manage.py makemigrations,或者执行了但没有重启服务,导致接口报no such column之类错误。我的习惯是:每次改完models.py,立刻在Pycharm的终端里连续执行两步,不要攒到后面再迁移。
另一个坑是外键关联删除。Django的on_delete参数如果没有设置,现在会报错,比如models.ForeignKey(Category, on_delete=models.CASCADE)。如果分类删了,它下面的商品要不要一起删?婴幼儿用品分类如果点错删除,可能导致商品数据全丢。后来我改成on_delete=models.SET_NULL,null=True,分类删除时商品保留,只把分类设为空。
5.3 包安装和环境问题
热词里总有“python安装numpy库的方法”“pycharm下载第三方库htmltestrunner”“pycharm怎么安装pandas包”,归根到底就是一个问题:Python包怎么装。方法只有三种:
- 命令行执行
pip install 包名 - Pycharm设置里打开Project Interpreter,点加号搜索安装
- 在有
requirements.txt的项目里执行pip install -r requirements.txt
如果安装很慢,就用国内镜像源。以清华源为例:
pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple还有一种情况是包安装成功了,但Pycharm里还是报ModuleNotFoundError,这通常是因为Pycharm当前解释器不是你这个项目的虚拟环境。在Pycharm右下角状态栏点击Python版本号,重新选择项目里的venv解释器就能解决。
至于“pycharm激活”,社区版完全免费,用社区版就够了。我做的婴幼儿用品项目从头到尾都是用社区版跑的,没必要折腾其他版本。
6. 项目功能优化与个人经验
6.1 几个值得做的优化点
主流程跑通之后,一定要留一些时间去打磨细节。我觉得以下三个优化点最加分:
第一,商品搜索。不要用简单的icontains模糊查询就完事,可以加一个过滤功能,按价格区间、适用月龄、销量排序组合筛选。后端用DRF的django-filter库,前端Vue配合下拉和滑动条,整个体验会专业很多。
第二,购物车数量和库存校验。前端虽然能限制数量,但后端必须再做一次校验。用户下单时要检查商品状态是否上架、库存是否足够,如果不校验,恶意请求可以把库存改成负数。这个校验写在订单创建视图里,查询商品时加上select_for_update()锁行,防止并发情况下的超卖。
第三,订单状态可视化。把待支付、已支付、待发货、已发货、已完成这些状态做成时间线,用户每次查看订单时能看到进度。这个功能本身不难,但能明显提升项目的完整度。
6.2 我最终选择这套方案的个人体会
这版项目我先后用Django和Flask各做过一版,最后的感受是:用Django写商城类网站确实舒服,很多功能不用重复造轮子,尤其是Admin后台能给运营带来极大便利;而Flask让我更明白路由、请求上下文、ORM这些底层概念。如果你是为了学习,我更建议先用Flask写一版小Demo理解原理,再用Django完成正儿八经的商城系统,这样两条路都走过,你对Python后端的理解会扎实很多。
我自己的项目后来还扩展了收藏功能、优惠券和下单短信提醒。婴幼儿用品网站还有一个特殊需求是“适用月龄推荐”,后来做成了一些简单的规则引擎代码放在后端,每次用户首页加载时根据搜索记录推送相关商品,效果还不错。这种从基础电商功能往外加业务逻辑的路径,是最适合练手和展示简历的方式。做这类项目,最重要的是先跑通一个最小闭环,然后再慢慢堆功能,千万不要一上来就想把所有模块全部做完,那只会让你卡在环境和模型设计阶段出不来了。