☰
基于Django的一站式家装服务管理系统设计与实现
2026/10/10 15:32:21 网站建设 项目流程

花了大半个学期帮师弟改同一类毕设题目,就是“基于Python的一站式家装服务管理系统”。第一次翻代码时发现,不少同学只是把“装修公司”换成了“商城”,预约、报价、施工进度全部硬塞进订单表,状态字段直接用字符串if判断,越往后写越乱。这个题要想做出彩,关键不是能跑几个CRUD页面,而是一开始就把“一站式家装”的业务链条理清楚,再围绕链条做状态流和权限设计。这篇文章把我实际做这套系统的完整思路写出来,从需求拆解、技术选型、数据库设计,到核心代码、测试部署、答辩演示,照着走能省下大量返工时间。

这套系统适合什么场景呢?业主装修时,线上看案例、约量房、看方案、比报价、签单、盯进度、验收付款,全程信息留在系统里;装修公司一边接线索、分配设计师,一边管理合同、材料、施工记录;平台运营方则要有后台可以查看所有订单状态和数据统计。简单说,系统要同时服务客户、设计师、公司管理员、项目经理这几类角色。正在做毕设或者想练手Python Web开发的同学,这篇可以作为一份落地参考。

1. 系统定位与需求拆解:清楚“一站式”到底指什么

1.1 家装业务链路里,系统要管住哪些节点

传统家装流程极其依赖电话和Excel,客户信息往往分散在销售、设计师、项目经理的聊天记录里。业主从接触装修公司到最终入住,中间经历大致是这样:浏览案例、提交预约、量房、出平面方案、报价谈判、签合同、拆改水电、泥瓦木工油漆、安装、验收、付尾款、评价售后。每个节点都会产生角色转换,比如预约之后变成客户,签合同之后变成业主,施工中又要靠项目经理维护工地。

“一站式”并不是一句营销话术,而是要把上面这一整条链路的业务对象建模到系统里。需要管理的对象至少包括:用户与角色、装修公司与设计师、预约单、设计方案、报价单、订单/合同、施工项目、施工进度记录、材料库存、评价信息。如果哪张表缺失,后面演示的时候就会卡壳。我见过不少同学只做了“用户、商品、订单”三张表,结果预约流程完全没法闭环,老师随便问一个“设计师怎么接单”就答不上来。

1.2 三类核心角色的真实痛点和功能清单

可以把这套系统当成一个多角色协作平台,而不是单一的后台管理软件。不同角色登录之后看到的内容和操作权限完全不同。

角色核心动作常用功能线下痛点
业主/客户找公司、约量房、看方案、签约、盯进度浏览案例、预约、确认方案、查看进度、评价电话沟通容易忘、没有流程记录
设计师接预约、传方案、做报价查看分配给我的预约、上传方案图、填写报价报价记录零散,改口说不清
公司管理员分配客户、审核方案、转施工单管理设计师账号、审核预约、生成订单、采购材料客户线索分散在个人微信里
项目经理/运营推进工地、上传照片、更新状态给施工阶段打勾、传进度照片、发起验收工地情况靠拍照发群,难汇总

整理成表之后你会发现,系统的核心不是界面做得多好看,而是每个操作背后有权限规则和数据状态。例如:业主不能直接把预约状态改成“施工中”,设计师不能绕过方案报价直接生成订单,项目经理只能更新自己负责的项目。这些约束要在后端做校验,而不是前端按钮隐藏一下就算完成。

1.3 为什么选Python而不是Java或PHP

毕设选型时,Python + Django 的组合足够稳,也足够有解释空间。我的判断依据有三点:

第一,Django自带Admin后台。家装系统的运营端需要录入公司资质、设计师简介、材料清单,如果用纯前端后台,这部分工程量会非常大。Django Admin可以替你省掉一个小程序的开发量,让运营人员直接在后台维护数据。

第二,ORM和用户认证开箱即用。Python生态里,Django的ORM非常成熟,外键关系、数据迁移、查询过滤都写得很顺手。系统需要多角色登录和权限控制,Django提供AbstractUser、LoginRequired、Group这些基础能力,能减少安全方面的坏味道。

第三,周边工具丰富。做演示数据可以用Faker,做接口可以用Django REST Framework,做报表想加分可以用pandas清洗数据,做爬虫抓案例素材也有requests/Scrapy。这些都是Java和PHP生态里相对费劲的地方。

当然也要诚实说一句:如果导师的项目是面向几十万用户的高并发平台,Python并不占优势。但作为毕业设计,我们要做的是“业务闭环完整、代码结构清晰”,这一点Python是最容易达到的。

2. 技术选型与项目结构设计

2.1 框架选型对比:Django还是Flask

选型时,很多同学会纠结用Django还是Flask。我建议直接用Django,除非题目明确要求轻量级架构。两者的区别可以看下面这张表。

对比项DjangoFlask
自带ORM有,迁移工具完善没有,需要另外装SQLAlchemy
Admin后台自带,非常省事需第三方扩展
用户认证完整认证体系需要手动实现
适合场景多模块、业务复杂单接口、原型演示
学习成本框架约定多,但规范灵活,但容易写乱

一套家装系统至少有用户、预约、方案、订单、施工、材料几个模块,如果用Flask,你得自己拼装一堆库,最后项目结构很可能是“一个大文件跑到底”,后期改不动。Django的app拆分方式反而更适合做这种多业务系统。

建议版本:Python 3.10及以上,Django 4.2 LTS。生产部署时可以切换到LTS版本,不会踩到新版本功能不稳定的坑。安装时直接创建虚拟环境:

python -m venv venv venv\Scripts\activate # Windows下激活虚拟环境 pip install Django==4.2.* mysqlclient pillow

这里顺手把pillow装好,因为方案图片上传、缩略图处理都离不开它。数据库开发阶段用Django默认的SQLite,部署或答辩前再切到MySQL,后面会讲到切换方法。

2.2 前后端方案:模板渲染还是接口分离

家装系统这种以表单、列表、状态流转为主的毕设,我推荐先用Django模板 + Bootstrap 5完成。原因是省掉跨域、Token、Node构建这些额外复杂度,把精力放在核心业务上。

如果题目要求微服务、前后端分离,就走Django REST Framework + Vue的方案。但我需要提醒一句,分离架构会让答辩重点从“业务逻辑”变成“前后端联调”,老师更关注的是你设计系统时的思考,模板渲染同样能把问题讲清楚。

页面层可以规划成这样:

  • 首页:装修案例展示、公司列表、设计师展示;
  • 预约页:选择一个公司/设计师,填写房屋信息和需求描述;
  • 个人中心:我发起的预约、我的订单、我的评价;
  • 订单详情:方案预览、报价明细、施工进度时间轴;
  • 后台:公司资料维护、预约分配、方案审核、物料管理。

前端使用Bootstrap栅格做响应式,量房预约页单独做一栏手机端样式,这个细节在答辩演示时很加分,能体现出你考虑了真实用户场景。

2.3 项目目录:按业务模块拆app

项目名建议叫home_services,避免用“system”这种过于泛的词。目录结构可以这么拆:

home_services/ ├── manage.py ├── requirements.txt ├── config/ │ ├── settings/ │ │ ├── base.py │ │ ├── dev.py │ │ └── prod.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ │ │ ├── models.py │ │ ├── views.py │ │ └── urls.py │ ├── companies/ │ ├── designers/ │ ├── appointments/ │ ├── schemes/ │ ├── orders/ │ ├── projects/ │ └── materials/ ├── static/ ├── media/ ├── templates/ └── tests/

这种结构的核心思路是“一个业务域一个app”,而不是把User、Company、Order全部堆在同一个models.py里。Django创建app很轻量,多拆几个不会有人嘲笑,反而显得有工程意识。settings拆成base/dev/prod三份是为了后续部署方便,开发时用dev.py,部署时用prod.py。URL也用include方式按模块挂在config/urls.py下,不要一条路由写完几十行。

3. 数据库设计与核心功能实现

3.1 核心表设计:让预约、方案、订单、施工形成闭环

数据库设计是这套系统最值得花时间的地方。我推荐用一张主表连接业务流,但每一段业务都要有独立的状态和记录。核心表大致如下。

表名核心字段说明
users_userusername, password, role, phone使用Django AbstractUser扩展,role区分角色
companies_companyname, license, manager, intro装修公司主体信息
designers_designeruser, company, specialty, years设计师关联公司和账号
appointments_appointmentcustomer, company, designer, address, demand, status预约单,status控制预约流程
schemes_schemeappointment, designer, title, cover, style, budget设计方案,可包含多张图片
orders_ordercustomer, appointment, scheme, total_amount, status订单/合同,金额用DecimalField
projects_constructionstageorder, stage_name, start_date, status施工阶段表
projects_progressrecordstage, description, photo, update_user施工进度记录
materials_materialname, spec, unit_price, stock材料库存
reviews_revieworder, rating, content, is_show完工评价

用户表的第一条规则:项目一开始就自定义User模型,继承AbstractUser并加上role字段。如果你先migrate了默认用户表再改,Django会给你一个很难受的提示“Can’t alter table”,届时只能重启数据库重新同步。自定义User的代码很简单:

from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES = [ (1, 'customer'), (2, 'company'), (3, 'designer'), (4, 'admin'), ] role = models.SmallIntegerField(choices=ROLE_CHOICES, default=1) phone = models.CharField(max_length=11, blank=True)

同时要在settings里写一行:AUTH_USER_MODEL = 'users.User'。写完这行再做第一次migrate,后面就顺了。

金额字段一定要用DecimalField(max_digits=10, decimal_places=2),不要用FloatField。我之前见过同学用float存报价,客户看到的金额出现0.30000000000000004,报价单直接翻车。这里不是“小细节”,是会让演示当场尴尬的问题。

外键的on_delete规则也要仔细想。例如订单里的appointment,如果预约单被删除,订单应该保留历史快照,外键应该设置on_delete=models.SET_NULL, null=True。如果直接CASCADE,删一条预约会把关联订单、施工记录全带走,这是灾难性的设计。

3.2 状态流设计:预约、订单、施工阶段怎么流转

状态设计直接影响代码复杂度。我建议用整数字段加choices,不要用字符串。字符串看起来直观,但写错了没提醒,数据库里会出现“施工中”、“施工中 ”、“施工中”三个不同值,后期报表完全没法统计。

预约状态可以这样定义:

APPOINTMENT_STATUS = ( (0, '待联系'), (1, '已联系'), (2, '已量房'), (3, '方案沟通中'), (4, '已签约'), (5, '已取消'), )

订单状态可以这样定义:

ORDER_STATUS = ( (0, '待支付定金'), (1, '设计进行中'), (2, '方案待客户确认'), (3, '待签订合同'), (4, '施工中'), (5, '待验收'), (6, '已完成'), (7, '已取消'), )

更关键的是不要在每个视图里随意赋状态值。我的做法是写一个独立的状态流转函数,专门判断当前状态能不能跳到下一个状态。比如“方案待客户确认”这个状态,设计师可以发起,客户可以确认,但不能直接从“待支付定金”跳到“施工中”。把规则收敛到一个地方,后面排查问题会非常爽。

3.3 用户登录与角色权限控制

Django自带的login和logout视图可以省不少事,我们只需要在模板里放表单,使用django.contrib.auth.views.LoginView即可。重点是权限控制。自定义一个简单的装饰器比引入复杂的权限库更实用。

from functools import wraps from django.http import HttpResponseForbidden def require_role(roles): def decorator(view_func): @wraps(view_func) def wrapped(request, *args, **kwargs): if request.user.is_authenticated and request.user.role in roles: return view_func(request, *args, **kwargs) return HttpResponseForbidden('当前角色没有权限执行此操作') return wrapped return decorator

使用方式很直观:

@require_role([1, 4]) # 只允许业主和管理员访问 def customer_appointments(request): ...

这种自定义装饰器评委看起来不复杂,又能说明白权限思路。如果每个视图都用if request.user.is_authenticated判断,代码会非常啰嗦,而且容易漏掉某个入口。

4. 核心代码流程与踩坑记录

4.1 预约业务:事务、重复提交和通知保持一致

预约是系统里最核心的业务入口。这里有一个容易踩的坑:客户连续点击两次提交按钮,会生成两条预约记录。处理方式是在视图里先检查是否存在进行中的预约,再创建新纪录,整个过程包在事务里。这样即使通知写入失败,预约也不会带着残缺数据落库。

from django.db import transaction from django.http import JsonResponse from .models import Appointment, Notification @require_role([1, 4]) @transaction.atomic def create_appointment(request): if request.method == 'POST': company_id = request.POST.get('company_id') address = request.POST.get('address') demand = request.POST.get('demand') expected_time = request.POST.get('expected_time') existed = Appointment.objects.filter( customer=request.user, company_id=company_id, status__in=[0, 1, 2, 3] ).exists() if existed: return JsonResponse({'message': '你还有进行中的预约,请先完成或取消再预约'}, status=400) appointment = Appointment.objects.create( customer=request.user, company_id=company_id, address=address, demand=demand, expected_time=expected_time, status=0 ) Notification.objects.create( user=appointment.company.manager, content=f'客户 {request.user.username} 发起了预约,请及时跟进' ) return JsonResponse({'appointment_id': appointment.id})

事务在这里起的作用是保证“预约单写入”和“通知写入”同时成功或同时失败。如果预约成功但通知失败,公司管理员完全不知道有人来了,业务链直接断掉。排查时会发现,很多看似玄学的问题都出在业务操作写到一半抛异常。

这里还想说明一点,有人会用Django signal在post_save里自动创建通知,代码表面更“优雅”,但会让控制流变得隐性。在毕设阶段,我建议用显式的service函数或视图内调用,讲代码时也好说清楚,后续再造一个service层来封装复杂的业务。signal留在真正需要解耦的地方再用。

4.2 方案图片上传:MEDIA配置和文件名处理

家装系统离不开效果图。在settings里要配置好MEDIA_ROOT和MEDIA_URL,否则上传的图片会跑到奇怪的位置。配置如下:

from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'

在根URL配置里加上开发环境的静态服务:

from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

模型里使用ImageField时,最好重写upload_to。我遇到过重名图片互相覆盖的问题,所以会用一个带时间戳的目录:

def scheme_image_path(instance, filename): ext = filename.split('.')[-1] return f'schemes/{instance.appointment_id}/{timezone.now().strftime("%Y%m%d%H%M%S")}.{ext}'

这个upload_to的好处是让每一条预约的方案图放在独立目录里,不仅不会覆盖,后台管理人员一眼就能知道哪个文件属于哪个订单。上传时前端还要限制文件类型和大小,我用的是accept="image/*"加后端校验,文件后缀只允许jpg、png、webp,大小限制在5MB。毕设阶段,这些简单校验已经足够,比只在前端隐藏按钮靠谱多了。

4.3 状态变更时如何给用户发通知

家装系统里,用户最关心的是“我的方案有没有出来”“工地进展怎么样”。所以状态变更时同步发送站内信是刚需。我设计了一个Notification表,字段包括接收用户、内容、是否已读、创建时间。在状态流转函数中统一发通知。

订单状态从“方案待客户确认”变成“施工中”时,要给客户发一条站内信,同时给项目经理生成一个任务记录。这里的关键是不要在订单列表或详情视图里手动改order.status,而是调用一个统一的update_order_status(order, new_status, operator)函数。函数里先做权限和状态校验,再更新状态,再创建通知。

这段代码逻辑虽然不难,但做出来后整个项目看起来是有设计的。老师问“你们怎么保证状态不跳位”的时候,你可以直接指着这个函数说:所有入口都走同一套校验,不允许直接赋值。

5. 测试、部署与答辩演示要点

5.1 核心流程写单元测试,不要只靠手工点击

毕设阶段写满覆盖率的测试不是必须,但至少要为核心业务流程写几个TestCase。Django的测试框架很友好,可以直接模拟登录和POST请求。

我为这套系统写过一组测试,覆盖的顺序是:客户注册登录、创建预约、公司管理员推进预约状态、设计师上传方案、客户确认方案生成订单、项目经理上传施工进度、客户验收并评价。这组测试跑通,基本代表主流程没大问题。

from django.test import TestCase, Client from apps.users.models import User class AppointmentFlowTest(TestCase): def setUp(self): self.client = Client() self.customer = User.objects.create_user( username='customer1', password='test123', role=1 ) # 准备一个测试公司,这里用create方便快速构造 def test_create_appointment_without_login(self): resp = self.client.post('/appointments/create/', {}) self.assertEqual(resp.status_code, 302) # 未登录会跳转 def test_duplicate_appointment_blocked(self): self.client.login(username='customer1', password='test123') # 先提交一次,再提交第二次,第二次应返回400

测试的好处还有一个:答辩前整理数据时,如果改了数据库结构导致某个页面报错,跑一遍测试立刻能定位到哪一环断了,不用一个个页面点过去。配合一份演示用的fixture数据,可以大大降低翻车概率。

5.2 从开发到部署:切换MySQL、配置Gunicorn和Nginx

毕设系统一般只需要单个云服务器或者PythonAnywhere。如果本地一直用SQLite,部署前切换MySQL时要注意几点:

第一,安装驱动:pip install mysqlclient,Windows下如果编译困难,可以换成pymysql并安装cryptography再配合django.db.backends.mysql。

第二,settings里的DATABASES改成:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'home_services', 'USER': 'home_user', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', } }

第三,部署环境里把DEBUG设为False,ALLOWED_HOSTS写上域名或IP。运行python manage.py collectstatic把静态文件收集到指定目录,再用Nginx托管。

启动命令可以写成:

gunicorn config.wsgi:application -b 127.0.0.1:8000 --workers=3

Nginx里主要配置两个location:

location /static/ { alias /path/to/staticfiles/; } location /media/ { alias /path/to/media/; }

如果不想装服务器,用PythonAnywhere的免费实例也够演示,它可以直接指定WSGI文件和虚拟环境,几分钟就能上线。总之生产方案不需要多惊艳,稳定、跑通、能解释就行。

5.3 答辩演示脚本:提前跑通三个关键场景

答辩现场最怕的不是答不上来,而是演示时临时输数据、找按钮。我的建议是把演示流程固定成三条主链路,数据预先填好:

场景一:业主登录,浏览首页案例,找一个装修公司发起预约,填写房屋需求。此时后台预约列表出现一条新记录。

场景二:管理员登录后台,把这条预约分配给设计师,设计师上传方案图并填写预算报价。然后切回客户账号,在订单页确认方案并生成合同。

场景三:管理员把订单状态切到施工中,项目经理上传工地照片和进度描述,完成后客户确认验收,提交评价。

这三条链路串完,基本覆盖了系统所有关键功能。演示时要故意展示一次权限拦截,比如用客户账号去访问设计师后台,页面提示“当前角色没有权限”,这点比功能正常展示更能体现系统设计能力。

6. 常见问题与排查技巧实录

6.1 家装系统开发中的典型报错速查表

这些坑都是我做这套系统时真实遇到过的,按报错原因和解决方案整理成一张速查表。

现象根本原因解决方法
运行python manage.py runserver提示No such table还没执行迁移运行python manage.py makemigrations && python manage.py migrate
提示App contains migrations with pending changesmodel字段改了没生成新迁移重新makemigrations,不要手改迁移文件
登录后点页面报CSRF token missing模板Form里少了csrf_token在form中加{% csrf_token %},或Fetch请求加X-CSRFToken头
图片上传后页面显示404MEDIA_URL没有配置或未添加static规则检查settings和根urls,生产环境检查nginx alias
中文数据写入数据库显示乱码数据库字符集不是utf8mb4建库用CREATE DATABASE ... CHARACTER SET utf8mb4
用SQLite时并发稍高报database is lockedSQLite写锁限制开发环境才用SQLite,演示转MySQL,或开启WAL模式
外键循环依赖卡在migration两个app互相引用了对方Model在其中一个外键使用字符串引用apps.xxx.Model,并谨慎设计依赖方向
模板找不到某个页面应用未加到INSTALLED_APPS检查setting INSTALLED_APPS,别忘了也要有templates路径

6.2 从这些坑里总结出的实操原则

第一个原则:用户模型要最先自定义。第一次migrate前就设置AUTH_USER_MODEL,后面所有外键引用都依赖它。如果项目已经做了一半才想起加role字段,尽量迁移到新库,别在原库上反复折磨。

第二个原则:状态字段用IntegerField加choices,并封装状态流转函数。字符串状态在演示时看不出问题,但到了统计报表环节会非常痛苦。在一个地方统一管理状态跳转,比每个视图各写一套if要可靠得多。

第三个原则:外键删除策略不要无脑CASCADE。订单、施工进度这类历史数据一定要用SET_NULL或PROTECT保护起来。真实系统里,一条订单被级联删除是事故级的错误,毕设阶段养成好习惯,答辩时也能讲出取舍依据。

第四个原则:演示数据用脚本生成,别手工造。写一个python manage.py seed_demo_data,用Faker生成20个客户、5家公司、50条预约、若干条进度记录。每次答辩前清空数据库重新seed,保证现场数据是“干净、统一、好看”的。

第五个原则:静态资源和媒体文件路径从第一天就规范化。开着DEBUG时runserver会自动处理,但关了DEBUG之后,404会立刻找上门。尽早配好Nginx的static/media,后面心情会好很多。

做这套系统最大的体会是:技术难点其实不深,最难的是把家装业务的每个状态和权限约束想明白。画好状态机,写代码只是翻译的过程。如果再有人问我这个题目怎么做,我还是会建议先花两天时间把表关系、状态流、角色权限画清楚,再动手写第一行Python代码,这比急着安装Django重要得多。最后补一个小技巧:答辩演示时,把后台管理操作和前端操作分开,前台只走一遍业务闭环,后台用Admin快速展示数据变动,整个节奏会非常稳。

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

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

立即咨询