资讯动态

影楼管理系统开发实战:Django与Flask选型及全流程实现

发布时间:2026/10/13 7:36:20 来源:尧图企业网站定制
拿到这个项目标题的时候我直接想起去年帮本地一家小影楼做内部管理系统的日子。婚纱影楼管理系统听起来好像就是一套普通的“增删改查”但真正做进去之后才发现这个行业的业务逻辑跟想象中的完全不一样。客户的订单状态不是只有“待定”和“完成”它牵扯到选片、精修、排版、出册、取件等一长串环节档期不是随便填个日期摄影师和化妆师的时间冲突会直接导致赔钱就连一个简简单单的“套系”表都要拆出服装套数、入册张数、相框材质、底片是否全送一堆字段。再加上标题里写的“django-flask”这其实不是让两个框架一起用而是让你在动手之前先做一个二选一的技术选型。这篇文章就围绕这套影楼管理系统把我从需求梳理、技术选型、数据库建模到前后端联调、部署上线的完整过程写出来给准备做同类项目的同学当参考。如果你也是用 PyCharm 做开发、用 Vue 写前端、正准备在 Django 和 Flask 之间纠结的这篇应该能帮你省下不少弯路。1. 先做两件事业务走访和端到端的数据流梳理很多做这类管理系统的人第一个动作就是打开 PyCharm 新建项目然后把后端跑起来。我理解这种冲动毕竟标题里技术栈都写好了谁不想赶紧看到页面出来。但我建议你先把 IDE 放到一边先花一到两天搞清楚一件事影楼到底是怎么运转的。1.1 影楼管理系统和普通进销存系统的根本差别普通进销存系统核心是商品、库存、出入库数据流相对简单。影楼管理系统不一样它的核心是“订单生命周期”。从客户第一次咨询开始到拍摄完成、选片、设计、制作、取件这个链条可能跨越两三个月。在这个周期里同一个客户会反复接触影楼好多次每一次接触都会产生新的子记录。举个例子一个客户下了订单付了定金摄影师拍完照片之后客户需要到店里选照片。选片的时候客户可能会加钱多买几张精修也可能不要那些超出的底片这个行为通常是独立的“二销”记录。接着设计部做相册排版排完还要让客户确认确认之后才能送工厂制作这些环节都需要状态标记。如果只在需求文档里写“客户管理”和“订单管理”四个大字等到开发阶段再对着一个简陋的数据表补来补去后面一定会返工。1.2 把整个业务流程落成一张数据流清单我用的笨办法是找影楼的实际负责人聊把他们的日常工作拆成一张清单。复盘下来最终确定的核心模块是六个客户管理、套系管理、订单管理、档期管理、产品制作、数据统计。这六个模块不再是抽象的名词而是跟真实业务一一对应的。客户管理管的是“客户池”。影楼不像电商客户不会看了就买多数会先咨询、上门、谈单有的甚至会犹豫一两个月所以必须有状态区分意向客户、已到店客户、已下单客户、流失客户。如果是纯 CRUD你根本没法回答“这个月的意向客户转化率是多少”这种最基本的经营问题。套系管理管的是“商品”。每个套系包含服装几套、摄影场景几个、精修张数、入册张数、相册尺寸、摆台数量、底片是否全送等这些字段直接关系到订单价格和后期制作成本。订单管理是绝对的主线。订单要关联客户、关联套系、关联摄影师和化妆师还要记录定金金额、尾款应付时间、订单状态。订单状态要多啰嗦一句我见过不少半成品项目把订单状态做成一个单纯的“已支付/未支付”这在影楼场景下是完全不够用的。后面的章节我会专门写状态机设计。档期管理是杀招。婚嫁行业有明显的旺季周末和农历宜嫁娶的日子摄影师、化妆师、化妆间、车棚都是稀缺资源。档期管理必须做到按天、按人看排班并且能在下单的时候迅速判断某个摄影师当天有没有约。产品制作管的是“后续交付”。订单拍完不等于结束要记录选片状态、设计排版状态、客户确认状态、工厂制作状态、取件状态。这一条线做清楚了影楼内部的跨部门协作才能跑起来。数据统计则是给老板看的月度成交笔数、套系销售量、摄影师工作量、二次消费金额这些数据不用太复杂但必须有真实的数据支撑不能拼凑。把这块需求理清楚之后我再回过来看标题里的技术栈心里就踏实了这其实就是一个典型的“管理信息系统 派单系统 轻量报表系统”的组合难度不在技术而在业务建模。2. 技术选型Django还是Flask以及为什么我最后选了Django标题里写的是“django-flask”很多同学会以为这两个框架要一起上或者随便选一个就行。实际上它们是两种风格差异很大的东西而且在这个项目里没有“两个一起用”的必要。我的建议是必须二选一选完之后不要回头。2.1 Flask的灵活和Django的“全家桶”到底差在哪先讲 Flask。Flask 是真轻量一个文件就能跑起来路由、请求、响应都非常直观适合写 API 或者做微服务。但如果一个影楼管理系统用 Flask 写你自己要解决的东西就有一长串数据库迁移工具、ORM 选型、表单验证、Admin 后台、分页、权限控制、JWT 登录……这些不是不能加每个 Flask 项目都会在某个时间点重复造一遍类似的轮子。Django 就是另一种风格。它自带 Admin 后台、内置 ORM、自带 Migration 迁移机制这些都是管理系统的刚需。Django 的 ORM 在复杂外键和聚合查询上非常省心比如要统计“本季度每个套系的销量”一行聚合函数就能查出来。再加上 Django REST framework简称 DRF这个第三方库写接口的效率比纯 Flask 要高不少。所以在这个项目里我最终选了 Django。理由就一条影楼管理系统 90% 是结构化数据的管理和查询Django 全家桶直接把这块地基打好了我可以把时间花在业务逻辑上而不是补框架缺失的零件。2.2 数据库用MySQLORM和原生SQL的取舍数据库我用的 MySQL版本 8.0。MySQL 在 Windows 和 Linux 上都很成熟对于这种中小型管理系统足够用了。用 ORM 可以直接建表但 MySQL 的字符集和时区这两个地方要提前处理否则后面会出现中文乱码和日期差 8 小时的问题。我一般在创建数据库时就指定字符集像下面这样CREATE DATABASE wedding_studio CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;ORM 的模型写好后用 Django 的 migration 自动建表不需要手写 DDL。但在两种场景下我还是会写原生 SQL一种是复杂的多表汇总报表用 ORM 拼会让代码变得非常绕另一种是排查慢查询的时候直接拿 SQL 去数据库里分析。ORM 和原生 SQL 不是互斥的Django 里用connection.cursor()执行原生语句很方便遇到复杂统计就直接 SQL 一把梭。2.3 后端工程在PyCharm里的落地结构PyCharm 是我在那段时间的主力 IDEProject 结构按 Django 的标准 layout 来命名清晰不要随便改动默认目录。一个能跑又能维护的后端工程长这样wedding_backend/ manage.py config/ # 项目配置文件 settings.py urls.py wsgi.py apps/ customer/ # 客户管理 package/ # 套系管理 order/ # 订单管理 schedule/ # 档期管理 product/ # 产品制作 dashboard/ # 数据统计 utils/ response.py auth.pyPyCharm 里有两个容易踩的坑。第一Python 解释器要选对。如果项目用了requirements.txt最好在 PyCharm 里新建 virtualenv 然后一键安装依赖不要用全局环境不然某一天装了个包把所有依赖都升级了项目就起不来了。第二Run/Debug Configuration 里把环境变量配好。我在本地习惯把DJANGO_SETTINGS_MODULE指到config.settings.dev生产环境用单独的prod.py这样本地调试和生产部署互不干扰。启动参数就是runserver 0.0.0.0:8000方便前端联调时从局域网访问。至于标题里的“-Vue”前端我用 Vue 3 Vite 构建组件库选了 Element Plus。Vue 2 在 2023 年底已经停止维护了新项目没必要再用旧版。后面联调章节我会细讲前端框架的选择原因。3. 数据建模影楼业务里最该花时间的地方如果一篇讲管理系统开发的博文只写了框架配置那等于没写。真正拉开项目质量差距的是数据模型设计。影楼管理系统尤其如此业务环节多表与表之间的关系复杂建模做得不好后面每一层都在还债。3.1 客户表、套系表、订单表的核心字段设计先看客户表。客户信息不只是姓名手机号我把“来源渠道”和“客户状态”这两个字段放进去了。来源渠道包括美团、抖音、小红书、门店自然进店、老客转介绍等状态字段用来标记销售进度。class Customer(models.Model): name models.CharField(max_length50) phone models.CharField(max_length20, uniqueTrue) source models.CharField(max_length20, defaultnatural) # 自然进店、线上渠道 status models.CharField(max_length20, defaultintention) # intention-意向, visited-到店, ordered-已下单, lost-流失 created_at models.DateTimeField(auto_now_addTrue)再来是套系表。套系字段看起来多但都有明确业务含义。我要特别提醒一下“底片全送”这个字段它直接影响客户是否愿意加购精修是影楼定价策略里非常关键的一环。class Package(models.Model): name models.CharField(max_length100) price models.DecimalField(max_digits10, decimal_places2) dress_count models.PositiveIntegerField(default1) # 服装套数 scene_count models.PositiveIntegerField(default1) # 场景数 photo_count models.PositiveIntegerField() # 精修张数 album_count models.PositiveIntegerField() # 相册本数 album_size models.CharField(max_length20) # 相册尺寸 base_negatives_all models.BooleanField(defaultFalse) # 底片是否全送订单表是系统里最复杂的表。它既要跟客户、套系关联又要记录摄影师、化妆师、拍摄日期还要管定金和尾款。我把订单表设计成三个层次订单主表、订单明细表记录二销加项、订单状态日志表。订单主表存一次性信息状态日志表记录每一次状态变化的时间和操作人方便后续追责和统计。3.2 档期冲突检测的数据库实现思路档期冲突是影楼管理系统里最容易写崩的功能也是最值得写的业务点。一开始我的想法很简单档期表里加一个is_booked字段下单的时候就查这个日子有没有被占。结果上真实数据之后立刻发现问题一个摄影师一天可能接好几个不同类型的任务有的单子是全天套系有的单子仅仅是上午出外景、下午就回来了单纯一个布尔值根本没法判断。最终我用的方案是在档期表里记录人员、日期、时间段、任务类型下单时对同一人员的同一天做时间段重叠判断。字段设计如下class Schedule(models.Model): studio models.ForeignKey(Studio, on_deletemodels.CASCADE) staff models.ForeignKey(Staff, on_deletemodels.CASCADE) date models.DateField() start_time models.TimeField() end_time models.TimeField() order models.ForeignKey(Order, nullTrue, blankTrue, on_deletemodels.SET_NULL) remark models.CharField(max_length200, blankTrue)判断冲突的逻辑用 Django ORM 写规则是“新时间段开始时间小于已占用结束时间并且新时间段结束时间大于已占用开始时间”写成代码就是conflict Schedule.objects.filter( staffstaff, datedate, start_time__ltend_time, end_time__gtstart_time, ).exists()这个start_time__lt和end_time__gt的组合就是区间重叠检测的经典写法时间上有没有重合一看便知。前端下单时点击一个日期后端实时返回该日期每个摄影师的空闲时段前端用 Element Plus 的时间选择器限制可选时间范围双重校验在体验上非常关键。3.3 状态机的选择订单状态用IntegerField还是CharField订单状态这个问题我栽过一次跟头。刚开始图省事用了IntegerField1、2、3、4 分别代表不同状态写代码时满脑子都是“1 是定金已付吗不对1 是已预约吧”这种混乱。后来改成CharField choices语义清晰了很多。class Order(models.Model): STATUS_CHOICES [ (reserved, 已预约), (paid_deposit, 定金已付), (shot, 拍摄完成), (selected, 选片完成), (designed, 设计排版中), (confirmed, 客户已确认), (producing, 制作中), (finished, 已完成), (cancelled, 已取消), ] status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultreserved)把状态定义为字符串就算你把状态值打印出来也能一眼看懂它到底是什么。更重要的是从“定金已付”到“拍摄完成”再到“选片完成”这是一条完整的业务流前端可以在订单列表页根据当前状态高亮下一步操作按钮这就是状态机带来的直观价值。我只在真正需要记录状态历史的地方建了日志表。这个表每次订单状态变化时插入一条记录字段包括order、from_status、to_status、operator、created_at。这对于影楼内部追责和数据分析都很有用老板可以问“这个单子在设计部卡了几天”答案直接从这个日志表查。4. 后端接口与前端Vue的联调事情开始变得具体数据模型定了后端有了骨骼接下来就要让它长出肉。联调阶段是我整套开发里踩坑最密集的地方DRF 的序列化器、CORS 配置、Token 认证、文件上传每一个都值得单独拿出来说。4.1 Django REST framework的ViewSet和序列化器DRF 的 ViewSet 最香的地方在于它会告诉你“常规接口你已经不需要手写了”。我写了一个跨模块的通用 BaseViewSet把分页、搜索、状态过滤这些逻辑统一收口。class OrderViewSet(viewsets.ModelViewSet): queryset Order.objects.select_related(customer, package).all() serializer_class OrderSerializer filter_backends [SearchFilter, OrderingFilter] search_fields [customer__name, customer__phone, order_no]select_related是给查询优化用的能减少 SQL 里的子查询和多次查询。否则列表页要显示 20 个订单Django ORM 默认会对每个订单再查一次客户表N1 问题就出来了页面一多就能感到卡顿。序列化器我建议用嵌套序列化器返回关联对象的信息而不是只返回 ID。前端拿一个订单回来能直接拿到客户姓名和套系名称省掉前端再发起二次请求。下面这种写法很常用class OrderSerializer(serializers.ModelSerializer): customer_name serializers.CharField(sourcecustomer.name, read_onlyTrue) package_name serializers.CharField(sourcepackage.name, read_onlyTrue) class Meta: model Order fields [id, order_no, customer_name, package_name, status, total_price]4.2 CORS、Token认证、文件上传这三大坑前后端分离的第一个坑就是跨域。前端跑在 5173 端口后端跑在 8000 端口浏览器默认是不允许跨域携带 Cookie 和进行跨站请求的必须安装django-cors-headers并配置允许的源CORS_ALLOWED_ORIGINS [ http://localhost:5173, ]第二个坑是认证。管理系统不能裸奔我用的是djangorestframework-simplejwtJWT Token 方案。登录接口返回 access token 和 refresh token前端把 access token 存在 localStorage每次 axios 请求时在 header 里带上axios.interceptors.request.use(config { const token localStorage.getItem(access_token) if (token) { config.headers.Authorization Bearer ${token} } return config })第三个坑是文件上传。影楼系统里客户头像、套系展示图、精修作品都需要上传。Django 默认的MEDIA_ROOT和MEDIA_URL配置很基础但在开发环境下上传文件不显示是个常踩的坑。需要在主urls.py里加静态文件路由from django.conf import settings from django.conf.urls.static import static urlpatterns [...] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)4.3 Vue端组件拆解以“档期日历”为例子Vue 端我用了 Vue 3 的组合式 API配合 Element Plus 的el-calendar和el-select来写档期日历。这个组件是整个系统里交互最复杂的部分之一因为影楼的档期不是简单的“今天有约没约”而是要看每个摄影师被分配了什么任务。我先把档期数据在后端聚合好预排成一个数据结构前端直接遍历渲染。这个方案比前端拿到原始列表后自己算要简单稳定得多。组件里用到的核心逻辑是拿到选中日期请求后端该日期的所有档期记录然后按“摄影师 时间段 订单状态”展示在日历格里。el-calendar v-modelcurrentDate template #date-cell{ data } div classschedule-cell span{{ data.day.split(-).slice(1).join(-) }}/span div v-foritem in getScheduleByDate(data.day) :keyitem.id :classschedule-badge item.staff.type span{{ item.staff.name }}/span span{{ item.start_time.slice(0, 5) }}-{{ item.end_time.slice(0, 5) }}/span /div /div /template /el-calendar档期日历的核心体验是“一眼看出谁有空”所以颜色也要按人员角色区分摄影师用深色、化妆师用浅色、外景车使用蓝色。前端不要做复杂的日期区间计算把后端返回的 data 直接映射成 UI 就好后端把数据准备得越干净前端写起来越舒服。前端路由用 Vue Router 做了角色权限控制。影楼人员分店长、销售前台、摄影师、后期设计四类角色看到的菜单不同。我用的是一个简单的动态路由方案用户登录后后端返回这个角色允许访问的菜单 code 列表前端在 Router.beforeEach 里判断当前路由的 meta.requiredRole 是否在允许列表里不在就重定向到首页。5. 从本机跑通到部署以及被测试逼出来的细节本机环境一切正常只能说明“能跑”真正要拿到影楼店里用起来还得经历部署和真实数据检验。这部分的经验是我把系统从自己的电脑搬到服务器之后才积累起来的。5.1 生产环境部署方案Gunicorn Nginx MySQL部署我用的经典三件套Gunicorn 跑 DjangoNginx 反向代理MySQL 存数据。Python 环境下有经验的开发基本不会用 Django 自带的 runserver 上生产它性能不够且不够稳定。Gunicorn 的启动命令很简单gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 60Nginx 负责做静态文件服务和反向代理。将前端构建好的 dist 目录指向frontend_dist将后端 API 代理到 8000 端口server { listen 80; server_name your-domain.com; root /path/to/frontend_dist; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; } location /media/ { alias /path/to/media/; } }这里有个细节proxy_pass后面如果带了路径会替换掉匹配的 location 前缀如果没带路径就原样转发。我在第一次部署时因为少配了一行 alias导致上传的图片全部 404排查了半天才发现是静态文件路由没指对。部署时建议先直接用 IP 访问跑通了再绑域名。5.2 权限细化店长、前台、摄影师、后期不同角色看什么开发阶段我以为权限就是“管理员”和“员工”两种角色就够了结果真实跑起来才发现不行。前台只能登记客户和创建订单但她能看到所有客户的手机号摄影师只能看到自己的档期和当天拍摄任务绝对不能看到订单金额后期设计只能看到“设计状态”相关的记录不能操作定金和尾款。所以在 Django 里我用自定义 Permission 控制接口的访问粒度class OrderViewSet(viewsets.ModelViewSet): def get_queryset(self): user self.request.user if user.role photographer: return Order.objects.filter(photographeruser.staff) if user.role designer: return Order.objects.filter(status__in[selected, designed, confirmed]) return super().get_queryset()这套按角色过滤 queryset 的写法比在每个接口里 if 判断要清晰得多。前端再配合菜单权限和后端权限双重控制基本保证不会出现越权操作。真正上线之后老板很满意的一点就是摄影师登录系统只能看到自己的排期不用再群里喊来喊去问“明天我拍哪单”。5.3 上线后遇到的问题清单时区、并发、公共前缀路径上线不代表完事反而是一堆隐蔽问题的爆发期。我遇到的第一个问题就是时间。Django 配置里TIME_ZONE我一开始写了UTC结果前端显示的拍摄日期总是差 8 个小时。这种“本地对、线上不对”的时间问题十有八九是时区没统一。我最终的方案是数据库连接设置为本地时区Django 的TIME_ZONE设置成Asia/ShanghaiUSE_TZ设置为False避免 Django 先转 UTC 再转本地的时间损耗和混乱。第二个问题是并发。影楼旺季前台同时录单两个销售几乎同一秒操作同一档期就可能出现重复预订。虽然 ORM 查了冲突但查询和插入之间有时间差。我的解决办法是给档期表增加唯一约束UniqueConstraint(fields[staff, date, start_time])数据库层面直接兜底重复插入直接在数据库处报错。第三个问题是部署路径。Nginx 配置里如果用了/api/前缀Django 端也要跟着改。我在settings.py里加了一个FORCE_SCRIPT_NAME /api的配置让 Django 生成 URL 时自动带上前缀然后前端 axios 的 baseURL 统一走/api。这个不改的话前端请求发出的地址和后端路由不匹配到处都是 404。上线之后我还主动给系统加了一个“操作日志”模型。每一次删除订单、修改价格、调整档期都记录下来包括操作人的用户名和时间。这个功能在开发阶段几乎没人关注但真正交付之后影楼店长会拿它来查一个月前的误操作老板也会拿它来判断某个员工的工作量。做管理系统多留一条后路总比出问题抓瞎强。这套系统从需求梳理到稳定运行前后花了大概一个多月的时间。如果让我重新做一次我会在数据建模阶段花更多时间跟影楼的销售聊把客户状态和订单状态的枚举值一次定准后面改一次字段迁移一次还容易牵连前端接口代价确实不小。PyCharm Django 的组合优势在于能快速把项目立起来Vue 负责把页面做得像样两者之间用 JWT 连接整条链路清晰可控。最后提醒一句不要因为标题写了“django-flask”就想办法同时用两个框架选一个能让你把业务做扎实的比什么都重要。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑