☰
基于Django的化妆品销售数据分析系统开发实战指南
2026/10/21 5:48:04 网站建设 项目流程

如果你正在为课程设计或毕业设计选题目,django基于数据分析的化妆品销售系统这个组合可以说是一个性价比极高的选择。它把Web开发、数据库设计、数据分析、可视化报表以及系统交付串在同一条线路上,做完之后你不会只停留在“会写登录和增删改查”,而是能真正用数据讲出一点经营层面的故事。这篇文章我按自己做这类项目时的完整思路拆一遍,从需求拆解到部署交付,把踩过的坑提前标记出来,既适合拿来做课程设计参考,也适合想快速搭一个带数据分析能力的销售系统Demo的同学直接抄作业。

1. 课程设计需求拆解:别急着写代码

1.1 导师究竟想考察什么

很多人拿到“化妆品销售系统”这种题目,第一反应就是:用户登录、商品列表、购物车、下单,好像把CRUD做出来就完事了。如果只是这样,你的项目大概率只能拿个及格分。课程设计和毕业设计的评分点从来不是“功能多不多”,而是你有没有表现出完整的软件开发思维,尤其在题目里带着数据分析这四个字的时候,考察的核心会发生变化。

评审老师一般会看几个方面:需求分析是否清楚、系统架构是否合理、数据库设计是否经得起推敲、数据分析的结果是否贴合业务、界面是否可用、代码是否规范、文档能否让别人看懂。换句话说,你不需要做一个淘宝,但你需要证明你理解了一笔订单、一个商品、一个用户之间的关系,并且能从订单数据里汇总出销售额、品类趋势、热门单品这些有价值的信息。

还有一个隐藏考点:交付物。题目里明确写到“附源码、数据库、万字文档”,这就意味着源码能不能跑起来、数据库能不能迁移、文档是否覆盖从设计到实现的全过程,都会直接影响最终成绩。很多同学最后栽在“代码能跑但换一台机器就崩”“文档写了一万字却没有架构图“这种细节上。

1.2 系统功能边界划分

在开始编码前,建议先把功能边界画出来,做成一页纸的功能清单。化妆品销售系统不需要追求大而全,最核心的角色就两个:管理员和普通用户。

管理员侧重点:商品分类管理、商品信息维护、订单状态管理、用户管理、以及最重要的销售数据分析看板。普通用户侧重点:注册登录、浏览商品、加入购物车、提交订单、查看历史订单。数据分析部分可以拆成几个具体指标:总销售额、总订单数、商品分类销售额占比、每日/每月销售趋势、销量Top10商品、不同价格区间的商品受欢迎程度、用户复购情况等。

我建议把功能分成必须项和加分项两类。必须项保证你能顺利答辩,加分项让你和别人的项目拉开差距。必须项包括:用户认证、商品SKU管理、购物车、下单流程、订单管理、基础的数据统计页面。加分项包括:数据可视化图表、Excel报表导出、按月自动汇总、简单的用户消费行为分析、使用Redis做热点缓存、使用nginx部署演示。课程设计把必须项做到稳定就已经不错了,毕业设计建议至少把可视化看板做成亮点。

2. 技术选型与项目骨架:Django为什么够用

2.1 为什么用Django而非Flask或SpringBoot

Django属于Python生态里的重型Web框架,很多人觉得它“太庞大”,但对于课程设计这种场景,它的重量恰恰是优势。选型时可以简单对比一下:Flask轻量灵活,但是用户认证、Admin后台、ORM、表单校验这些都需要自己组装;SpringBoot功能强大,但受众面向企业级Java开发,学习曲线陡,如果只是做销售系统,用Java写CRUD会花掉大量时间在配置上。

Django自带的Admin后台是一个很强的加分项。你只需要把模型定义好,注册到admin里,就拥有了一套可以管理商品、订单、用户的后台页面。这意味着你可以把精力放在业务逻辑和数据分析上,而不是从零手搓一套管理界面。另外,Django的ORM封装了数据库操作,能让你在Python里直接写聚合查询、分组查询,这对后面做数据分析相当重要。

我见过不少同学用Flask做销售系统,最后卡在了复杂查询和用户认证上,因为Flask默认什么都要自己配,遇到跨表查询、一对多关联时写出来的代码很绕。Django的ORM配合模型关联,几行代码就能完成按月份分组求和,效率完全不是一个量级。

2.2 数据分析模块的取舍

项目名称里有“基于数据分析”,但不是让你上机器学习、深度学习。很多初学者一听到数据分析就头皮发麻,以为要训练模型、调参、跑算法,其实在销售系统里,大多数有价值的东西是描述性统计分析:今天卖了多少、环比增长多少、哪个品类卖得最好、哪种价格带最受欢迎。这些通过SQL聚合或者Django ORM的annotate、aggregate就能完成,不需要引入pandas,更不需要上TensorFlow。

当然,如果你的数据量到了几万条以上,或者你想展示更复杂的分析思路,也可以在Django里调用pandas处理数据,再返回JSON给前端。我个人的建议是:把“数据分析能力”体现在统计指标的设计上,而不是体现在用了多牛的库上。比如你设计一个“销售漏斗”:浏览商品人数、加购人数、下单人数、支付人数,这就能让评委眼前一亮,因为这是在思考业务,而不是在堆技术名词。

另一个取舍点:是否使用Django Rest Framework来写接口?如果只是简单的前后端交互,直接使用Django的模板渲染加JSON接口就够了。但如果你打算把前端页面拆成独立的Vue项目,那就建议上DRF,结构更清晰。课程设计为了控制工作量,我建议用Django模板系统渲染页面,数据图表部分单独用JSON接口返回,这样前端代码简单,又能满足图表库对数据格式的要求。

3. 数据模型与数据库设计:化妆品销售系统的核心

3.1 实体关系设计思路

数据库设计决定了后面所有查询和分析代码的复杂度。化妆品销售系统的核心实体有用户、类别、商品、购物车、订单、订单项,如果还想做更丰富的用户行为分析,可以加浏览记录或收藏表,但这不是必须的。

我的建议是至少设计五张核心表:用户表(可以用Django内置的User模型扩展)、商品分类表、商品表、订单表、订单商品明细表。如果你需要管理库存,再增加库存流水表;如果希望分析客户价值,再增加会员等级或积分字段。

来看一下关系:一个分类下有多款商品,商品与分类是多对一;一个用户可以下多笔订单,用户与订单是一对多;一笔订单包含多种商品,但为了保存下单时的商品快照(价格、名称可能后续改动),通常要把商品冗余到订单明细表里,所以订单与订单明细是一对多,订单明细与商品多对一。

这种设计能支撑绝大多数统计需求。比如统计某个月销售额,只要聚合订单表的金额字段;统计某分类销量,则需要通过订单明细表关联商品表,再对数量字段求和。如果只建了一张订单表而没有明细表,那么一个订单包含多个商品时就会设计得很难受。

3.2 模型定义与字段陷阱

直接给一份可以用的参考模型代码,注意这里用的是SQLite默认数据库,后续可以无缝切到MySQL,换数据库只需要改settings里的配置。

from django.db import models from django.contrib.auth.models import User class Category(models.Model): name = models.CharField('分类名称', max_length=100, unique=True) sort = models.IntegerField('排序权重', default=0) class Meta: verbose_name_plural = '商品分类' ordering = ['sort', 'id'] def __str__(self): return self.name class Product(models.Model): name = models.CharField('商品名称', max_length=200) category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products', verbose_name='所属分类') price = models.DecimalField('售价', max_digits=10, decimal_places=2) cost_price = models.DecimalField('成本价', max_digits=10, decimal_places=2, blank=True, null=True) stock = models.PositiveIntegerField('库存', default=0) sales = models.PositiveIntegerField('销量', default=0) description = models.TextField('商品描述', blank=True) image = models.ImageField('商品图片', upload_to='products/', blank=True, null=True) is_on_sale = models.BooleanField('是否上架', default=True) created_at = models.DateTimeField('创建时间', auto_now_add=True) class Meta: verbose_name_plural = '商品' ordering = ['-id'] def __str__(self): return self.name class SalesOrder(models.Model): order_no = models.CharField('订单号', max_length=32, unique=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders', verbose_name='下单用户') total_amount = models.DecimalField('订单总金额', max_digits=12, decimal_places=2) pay_status = models.PositiveSmallIntegerField('支付状态', choices=((0, '待支付'), (1, '已支付'), (2, '已取消')), default=0) receiver_name = models.CharField('收货人姓名', max_length=50) receiver_phone = models.CharField('收货人电话', max_length=20) receiver_address = models.CharField('收货地址', max_length=255) remark = models.CharField('备注', max_length=500, blank=True) created_at = models.DateTimeField('下单时间', auto_now_add=True) paid_at = models.DateTimeField('支付时间', null=True, blank=True) class Meta: verbose_name_plural = '销售订单' ordering = ['-id'] def __str__(self): return self.order_no class OrderItem(models.Model): order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='items', verbose_name='所属订单') product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='商品') product_name_snapshot = models.CharField('商品名称快照', max_length=200) price_snapshot = models.DecimalField('商品单价快照', max_digits=10, decimal_places=2) quantity = models.PositiveIntegerField('购买数量', default=1) subtotal = models.DecimalField('小计金额', max_digits=12, decimal_places=2) class Meta: verbose_name_plural = '订单明细' def __str__(self): return f'{self.order.order_no} - {self.product_name_snapshot}'

这里有几个实际踩过的坑,提醒一下。

第一,金额字段不要用FloatField,一定要用DecimalField,否则会出现0.1加0.2不等于0.3这种问题,金额一旦出错,后面所有销售额统计都是错的。第二,订单表名称不要直接叫Order,在Python里Order有可能和别的库的类名冲突,而且作为模型名不够直观,我习惯叫SalesOrder。第三,on_delete参数在Django 2.0以后必须显式声明,通常分类删除时商品也一起删除或者限制删除,这里用CASCADE,实际生产一般用PROTECT,课程设计里CASCADE简单省事。第四,为了让列表查询性能不太差,给外键和常用的筛选字段加上db_index=True,默认外键会自动建索引,但order_no这种经常搜的字段建议手动加索引。

如果你在本地启动之后发现Admin页面的中文显示有问题,检查一下settings.py里的LANGUAGE_CODE,改成zh-hans,TIME_ZONE改成Asia/Shanghai。很多人漏了时区配置,导致后面按天统计时时间全部偏移8小时,数据曲线看起来就怪怪的。

4. 数据分析核心实现:从订单表到可视化看板

4.1 统计维度与聚合逻辑

数据分析功能是整个项目能否从“普通管理系统”变成“有亮点系统”的分水岭。我建议把统计维度分成时间维度、商品维度、用户维度三类。

时间维度包括:按日销售额趋势、按月销售额趋势、不同星期几的销售热度。实现方式用ORM的TruncDate或TruncMonth,例如要统计最近30天每天的销售额:

from django.db.models.functions import TruncDate from django.db.models import Sum, Count from django.utils import timezone from datetime import timedelta # 取最近30天已支付的订单 start_date = timezone.now().date() - timedelta(days=29) orders = SalesOrder.objects.filter(pay_status=1, created_at__date__gte=start_date) daily = (orders .annotate(day=TruncDate('created_at')) .values('day') .annotate(total=Sum('total_amount'), count=Count('id')) .order_by('day'))

商品维度包括:销量排行、销售额排行、分类占比。要统计分类维度,需要先通过OrderItem关联Product,再关联Category:

from django.db.models import Sum category_stats = (OrderItem.objects .filter(order__pay_status=1) .values('product__category__name') .annotate(total_qty=Sum('quantity'), total_amount=Sum('subtotal')) .order_by('-total_amount'))

用户维度包括:消费额排名前10的用户、复购次数最多的用户、用户平均客单价。统计用户客单价可以这样写:

user_stats = (SalesOrder.objects .filter(pay_status=1) .values('user__username') .annotate(order_count=Count('id'), total_spent=Sum('total_amount')) .order_by('-total_spent')[:10])

用ORM而不是手写SQL的好处是,你不需要关心不同数据库的方言差异,而且返回值就是Python对象和字典,可以直接序列化成JSON送给前端图表。需要注意的是,TruncDate在SQLite和MySQL下都能正常工作,转成date类型后再分组,不会因为时区产生太大误差,前提是TIME_ZONE配对了。

4.2 图表数据接口的实现

前端图表需要的是JSON数据,所以我们不直接在模板里渲染统计结果,而是写一个独立的视图函数返回JSON。下面是一个典型的接口实现:

import json from datetime import timedelta from django.http import JsonResponse from django.utils import timezone from django.db.models import Sum, Count from django.db.models.functions import TruncDate from .models import SalesOrder, OrderItem, Product def sales_trend_chart(request): days = int(request.GET.get('days', 30)) if days > 90: days = 90 start_date = (timezone.now() - timedelta(days=days - 1)).date() rows = (SalesOrder.objects .filter(pay_status=1, created_at__date__gte=start_date) .annotate(day=TruncDate('created_at')) .values('day') .annotate(total=Sum('total_amount'), count=Count('id')) .order_by('day')) labels = [] totals = [] for row in rows: labels.append(row['day'].strftime('%m-%d')) totals.append(float(row['total'])) return JsonResponse({'days': labels, 'totals': totals})

同样,分类占比接口返回每个分类的销售总额和占比,Top商品接口返回销量最高的几个商品名称和数量。建议接口名称按照资源来命名,统一加/api/前缀,比如/api/trend/、/api/category/、/api/top-products/,这样看起来更专业。如果不想装Django Rest Framework,直接用JsonResponse是够用的,但要注意Decimal类型不是JSON可序列化类型,返回前要转成float或str,否则会报TypeError: Object of type Decimal is not JSON serializable。

有人可能会问,这些统计查询每次都实时计算,数据量大了会不会变慢?确实会。课程设计的数据库通常只有几百条假数据,实时计算完全没问题。但如果你想体现一点“性能优化”的意识,可以在模型里加一个SalesDailyReport表,每天定时汇总订单生成快照,查看趋势的时候直接读快照,这个思路写进文档里就是亮点。

5. 可视化页面与前端展示:图表要能讲出业务故事

5.1 页面布局与图表选型

数据算出来了,最终还是要让人看得懂。前端我建议首选Bootstrap + ECharts的组合,原因很简单:Bootstrap能让你快速搭建出干净的后台布局,ECharts的文档里全是可直接复制的中文示例,对新手极其友好。如果你挑了Chart.js也不是不行,但遇到折线图加数据缩放、饼图图例格式化这些需求时,ECharts的配置更灵活。

看板页面建议做成上下结构。顶部放一排汇总卡片:总销售额、总订单数、商品数、用户数。中间放一张销售趋势折线图,最好支持7天/30天/90天切换。下面左侧放分类占比饼图,右侧放销量Top10条形图。如果还要展示用户消费能力,可以加一个表格,显示消费排行前五的用户。这套布局做出来,哪怕配色很素,也能让人觉得项目很完整。

ECharts使用很简单,在HTML里引入cdn链接,然后通过axios或者jQuery的$.ajax去请求我们刚才写的JSON接口,把数据塞进setOption即可。例如,销售趋势图的数据加载大概是这样的:

fetch('/api/trend/?days=30') .then(response => response.json()) .then(data => { trendChart.setOption({ xAxis: { data: data.days }, series: [{ data: data.totals, type: 'line', smooth: true }] }); });

注意接口返回的是数组,和ECharts需要的格式刚好匹配,前端不需要做二次转换。分类占比图稍微特殊一点,需要在接口里同时返回name和value两个字段,因为在ECharts的饼图中,数据格式是{name:'护肤', value: 12345}。

5.2 异步加载与权限控制

做前端页面时,最容易出问题的不是样式,而是权限。数据分析看板属于管理员的专属页面,不能让普通用户随随便便就打开。Django里最简单的方式是使用@login_required装饰器,再加上@user_passes_test或者@staff_member_required来限制只有管理员可见。

from django.contrib.admin.views.decorators import staff_member_required @staff_member_required def dashboard_view(request): return render(request, 'admin_dashboard/index.html')

API接口同样需要做限制,否则任何人都能拿到统计数据。建议在需要受保护的接口上加上login_required:

from django.contrib.auth.decorators import login_required @login_required def sales_trend_chart(request): ...

前端页面里还需要注意Ajax请求的CSRF问题。如果你用的是fetch或者jQuery,并且请求方式包含POST,需要带上CSRF token。这里建议数据分析接口全部用GET请求,只读数据不需要做修改,这样CSRF问题就不涉及了。当然,下单、购物车这类写操作一定要用POST,并且把{% csrf_token %}放到表单里,或者通过请求头传递token。

另一个肉眼可见的坑是静态文件404。Django在调试模式下默认由框架处理静态文件,但前提是settings.py里配置好STATIC_URL,并且在在你urls.py里手动加上静态文件路由。如果你用python manage.py runserver能正常访问页面,但部署到服务器上样式全部丢了,大概率是STATIC_ROOT和collectstatic没有执行,课程设计里只需要保证本地能跑即可,不过建议还是把whitenoise或者nginx静态文件配置写进文档,可以提升项目完整度。

6. 部署与交付:让源码、数据库、文档都能稳跑

6.1 本地环境配置与种子数据

很多课程设计项目最后死在“换了一台电脑就跑不起来”。为了让别人能复现你的项目,环境配置必须写得像流水账一样清楚。我这里给一个最简步骤:

创建虚拟环境并安装依赖:

python -m venv venv source venv/bin/activate # Windows是 venv\Scripts\activate pip install django==4.2 pillow

如果后面切到MySQL,再装pymysql并修改settings里的数据库配置。课程设计用默认的SQLite就足够了,因为这能保证任何环境都能直接运行,不需要额外装数据库服务。

然后迁移数据库并创建管理员:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver

源码里通常需要附带一个requirements.txt,通过pip freeze > requirements.txt生成。数据库文件不要用真实的开发库,应该准备一份包含演示数据的SQLite文件,放在data/目录下,并在README里写明如何导入,或者在fixtures/目录放一份JSON格式的fixture数据,执行python manage.py loaddata fixtures/data.json即可。

我强烈建议在项目里写一个seed.py脚本,用来生成随机用户、商品、订单数据,这样别人运行项目后就能直接看到统计图表。数据量控制在300到500条订单即可,太少图表没意义,太多导入太慢。生成订单数据时注意让日期分布在一段时间范围内,比如过去90天,这样趋势图才会出现起伏,否则所有数据集中在一个时间点会让折线图变成一根直线。

6.2 常见问题排查技巧实录

做这个项目时我遇到过不少幺蛾子,整理成一张速查表给你。

问题现象可能原因解决办法
中文变成乱码数据库编码不是utf-8,或者没有设置LANGUAGE_CODE创建数据库时指定utf8mb4;settings中设置LANGUAGE_CODE='zh-hans'
分页显示所有数据忘了调用分页器使用Django Paginator,并在模板中渲染页码
图表接口返回500Decimal无法JSON序列化返回前把Decimal转成float
按天统计日期偏移TIME_ZONE没有设置设置TIME_ZONE='Asia/Shanghai',并启用USE_TZ
Admin后台美化样式丢失静态文件未收集使用python manage.py collectstatic
多表查询特别慢没有用select_related查询OrderItem时加select_related('product', 'order')
商品删除后订单明细丢失外键使用了CASCADE如果不希望删除历史订单,外键改用PROTECT
运行runserver时数据库锁死SQLite并发写导致临时现象,重启即可;毕设可改用MySQL

还有很多人在模板里直接写复杂的统计查询,结果每次刷新页面都要计算好几遍。建议把高频的统计结果用cached_property或者cache装饰器缓存一下,或者放到Admin的action里点击时生成Excel报表。比如,管理员在后台点一下“导出月度报表”,程序生成Excel文件,这个功能放在文档里也是加分项。实现可以用openpyxl库,写一个后台命令脚本,在management/commands/generate_report.py里生成Excel放到media/目录。

踩过的最深的一个坑其实是数据库迁移冲突。如果你动手改过模型字段,后来又改了回去,Django会记录历史迁移文件,有时候出现迁移文件依赖混乱。遇到这种情况我不建议去手动删除迁移记录,稳妥的做法是把相关app下的migrations/目录里的冲突文件删除,然后直接python manage.py makemigrations重新生成,再migrate。如果你有重要数据,记得先备份。课程设计项目数据不多,重建一下完全没关系。

另外一个经常被忽略但很重要的点是接口返回时间字段。如果你的JSON接口里包含了created_at的datetime对象,也不能直接序列化,建议在返回前手动格式化为字符串:row['created_at'].strftime('%Y-%m-%d %H:%M:%S')。如果你用DRF,这个问题由它的JSONRenderer处理,不用操心。但手写接口时一定要留意。

7. 从课程设计到答辩:一点个人经验

做完功能、写完文档、准备好Demo,最后一步是答辩演示。很多人在答辩时喜欢打开项目就开始点页面,但评审老师更想听的是你的设计思路。建议按这个顺序讲:数据模型怎么设计的(可以用PowerDesigner或draw.io画ER图)、数据分析指标是怎么定的、用什么方式实现统计、图表能说明什么业务问题、未来还能怎么扩展。全程控制在5分钟以内,具体操作演示选两三个重点页面就行。

我在做这个项目的时候,最大的体会是“数据分析”四个字一定要落到实处。你可以不写很高深的模型,但必须有完整的链路:原始订单数据 -> 数据清洗(过滤未支付订单) -> 统计聚合 -> 图表展示 -> 业务结论。哪怕只是从数据中发现“周六日销售额更高”“洁面产品占比超过40%”,这也是数据分析的价值。把这句话写在论文摘要里,比堆一堆技术名词有说服力得多。

如果你做完课程设计后还想把项目继续深化,可以考虑增加这几个方向:引入用户RFM模型来划分客户价值,用scikit-learn做简单的商品销量预测,或者把销售数据按省份/城市进行地域分析。升级到毕业设计时,只需要在现有框架上加一个独立的数据分析模块,并配套实验对比,工作量完全可控。希望这份攻略能帮你少走弯路,把精力花在真正能拿分的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询