拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python Flask + SQLite 构建理发店极简会员管理系统实战

简介这是一款专为中小理发店、社区店及夫妻店设计的轻量级会员管理源码系统聚焦真实经营场景解决客户信息分散、充值消费难追溯、员工业绩统计低效等痛点。资源包共74个文件含37个Java后端逻辑文件、5个Vue前端页面、4个CSS/JS样式与交互脚本、2个HTML入口页、1个SQL初始化脚本及1个PowerShell一键编译脚本配合SQLite本地数据库实现开箱即用整体仅376KB便于门店终端快速部署与分发。目前已有53人学习下载。开发者已提供完整前后端工程结构、租户隔离鉴权体系、带校验码的会员安全消费流程、经营概览与审计日志等核心模块代码注释清晰目录组织规范含frontend/src、src/main/java、docs/screenshots等标准分层适合JavaVue技术栈初学者理解轻量SaaS系统设计逻辑也便于店主直接打包运行落地使用。1. 项目概述为什么理发店需要一个“极简”会员系统干了十几年软件开发也帮不少实体小店做过信息化改造我发现一个挺有意思的现象越是像理发店、社区超市、夫妻早餐店这类“小而美”的生意越容易被市面上那些功能繁杂、价格不菲的“专业”软件给劝退。老板们不是不想管好客户、不想搞会员制而是面对那些动辄几十个菜单、需要专人培训才能上手的系统第一反应往往是“太复杂了我用不上”。最后客户信息记在本子上充值消费靠心算和口头约定不仅效率低还容易出错、引发纠纷。这个“理发店极简会员管理系统”项目就是针对这个痛点来的。它的核心定位非常清晰面向中小理发店、社区店、夫妻店的一体化轻量客户管理工具。关键词是“极简”和“轻量”。这不是一个要取代大型连锁店ERP的庞然大物而是一个开箱即用、上手就会、聚焦核心业务的小工具。它要解决的就是最实际的那几个问题谁来剪过头充了多少钱还剩多少这次消费打了折没老板能快速查账、店员能快速操作这就够了。从技术实现上看这类项目通常不会追求最新的微服务、中台架构而是采用最务实的技术栈比如Python的Flask/Django框架或者PHP的ThinkPHP/Laravel配合一个轻量级的数据库如MySQL或SQLite。源码开放的意义在于让懂点技术的店主或开发者能够根据自己店铺的实际情况进行微调比如修改一下会员等级名称、调整一下折扣规则甚至集成一个简单的短信提醒功能。它的价值不在于技术有多高深而在于“恰到好处”地解决了真实场景下的具体问题。2. 核心需求解析与功能设计思路在动手写代码之前我们必须先搞清楚一个理发店的老板每天需要用它来做什么。脱离实际需求的功能都是累赘。2.1 中小理发店的四大核心管理痛点客户识别与历史记录模糊老顾客来了除了脸熟记不住他上次是几号来的、剪的什么发型、有没有提过特殊要求比如鬓角修短点。这导致服务体验无法延续顾客觉得你不重视他。充值消费账目混乱手工记账容易记错、算错。充500送100实际消费了80余额到底该是多少不同的店员算法可能不一样容易引发顾客对账目的不信任。会员权益与促销活动执行难想搞个“周一老人八折”或者“烫染套餐优惠”但靠人工记忆和计算效率低且易出错。活动效果也难以统计。经营数据黑洞每天忙忙碌碌但到底哪个发型师业绩最好哪种服务项目最受欢迎月度营业额是多少纯靠感觉缺乏数据支撑决策。2.2 “极简”系统的功能边界划定基于以上痛点我们的系统功能必须做减法聚焦最核心的闭环会员管理增、删、改、查。记录姓名、电话核心标识、备注如“发质较软”。这就是数字化的“客户名片”。账户管理核心是储值余额。支持充值记录充值金额、赠送金额、支付方式、消费扣款记录消费项目、原价、折后价、实扣金额。每一笔变动都必须有清晰流水。服务项目管理一个简单的价目表。洗、剪、吹、烫、染、护理等定义好名称和标准价格。这是消费扣款的基础。消费收银与流水最常用的操作界面。选择会员 - 选择消费项目 - 系统自动计算折扣如会员价- 从余额扣款或收取现金 - 打印或生成简易消费凭证。同时所有操作生成不可篡改的流水记录。数据统计看板极简版不需要复杂的BI只需几个关键数字今日营业额、当前会员总数、会员总储值余额、热门项目排行榜。让老板一眼掌握经营概况。哪些功能要果断舍弃复杂的库存管理发胶、染膏库存、复杂的排班预约系统、多维度的营销活动引擎发券、拼团。这些功能对于单店或两三人的小店来说维护成本高于收益。我们的设计原则是一个主界面完成80%的日常操作三次点击之内达到目标。3. 技术选型与架构设计如何实现“轻量”与“一体”既然目标是轻量和极简技术选型上就必须选择那些学习曲线平缓、部署简单、资源消耗低的方案。3.1 后端技术栈Python Flask SQLite 组合拳为什么是Python和Flask对于这类小型管理工具开发效率和高可读性至关重要。店主可能找个兼职学生开发者就能维护。Flask是一个“微框架”它不像Django那样自带“全家桶”而是允许你从零开始按需添加组件这正好符合“极简”的哲学——你需要什么就装什么没有冗余。# 一个非常简单的Flask应用骨架展示“极简”思想 from flask import Flask, render_template, request, jsonify import sqlite3 app Flask(__name__) # 数据库初始化SQLite单文件无需安装数据库服务 def init_db(): conn sqlite3.connect(barbershop.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS members (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT UNIQUE NOT NULL, balance REAL DEFAULT 0.0, remark TEXT)) conn.commit() conn.close() app.route(/) def index(): # 主界面可能集成了会员查询、快速消费等功能 return render_template(index.html) app.route(/api/member/charge, methods[POST]) def charge_member(): # 会员充值API轻量级的JSON接口 data request.json # ... 业务逻辑更新余额插入流水记录 ... return jsonify({success: True, new_balance: 100.0}) if __name__ __main__: init_db() app.run(debugTrue) # 调试模式生产环境需关闭SQLite数据库的选择是点睛之笔。它不需要像MySQL或PostgreSQL那样单独安装和配置数据库服务整个数据库就是一个.db文件可以随程序一起拷贝、备份。对于单机版的小店管理系统来说它的性能完全足够且管理成本为零。3.2 前端设计单页应用(SPA)还是多页应用(MPA)对于极简系统我强烈推荐使用基于模板的多页应用(MPA)而不是引入Vue/React等框架构建单页应用(SPA)。理由如下开发更简单直接使用Jinja2Flask默认模板引擎渲染HTML搭配一点JavaScript如jQuery处理交互足够应对表单提交、数据验证和简单DOM更新。部署更省心构建和打包步骤被极大简化没有复杂的Webpack配置。前端就是一些静态的HTML、CSS、JS文件。符合操作习惯店内的操作电脑通常配置不高MPA每次请求刷新页面虽然体验上不如SPA流畅但更稳定也更符合传统管理软件的操作直觉。前端UI框架可以选择轻量级的如Bootstrap或Bulma它们提供了现成的、响应式的组件能让系统在电脑、平板甚至大屏手机上都有不错的显示效果而且风格统一专业。3.3 一体化部署真正的“开箱即用”“一体化”意味着交付给店主的应该是一个尽可能简单的包。理想状态是双击一个图标程序就运行起来了。这在技术上可以通过以下方式实现打包为可执行文件使用PyInstaller或cx_Freeze将Python脚本、依赖库和SQLite数据库文件一起打包成一个.exeWindows或可执行程序macOS/Linux。用户无需安装Python环境。内置轻量级Web服务器Flask自带的开发服务器不适合生产环境。可以换成Waitress或Gevent这类纯Python的WSGI服务器它们性能更好且可以一并打包。配置自动化首次运行时自动检查并创建数据库文件、初始化表结构。提供一个简单的配置界面或配置文件让店主设置店名、LOGO、端口号等。这样最终交付物可能就是一个压缩包解压后里面有一个启动.exe和一个data文件夹存放数据库。店主需要做的就是双击运行。注意这种单机版部署方式数据存储在本地。必须提醒店主定期备份data文件夹下的数据库文件。可以编写一个简单的脚本每天自动将数据库拷贝到U盘或网盘。4. 核心功能模块的详细实现与代码剖析让我们深入到几个核心功能的代码层面看看如何用最简洁的代码实现稳定可靠的功能。4.1 会员与账户管理数据模型设计这是系统的基石。设计不好的数据表后期会带来无数麻烦。# models.py - 数据模型定义 import sqlite3 from datetime import datetime class Database: def __init__(self, db_pathbarbershop.db): self.conn sqlite3.connect(db_path, check_same_threadFalse) self.conn.row_factory sqlite3.Row # 使查询返回字典-like的对象 self.create_tables() def create_tables(self): cursor self.conn.cursor() # 会员表 cursor.execute( CREATE TABLE IF NOT EXISTS member ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT UNIQUE NOT NULL, -- 手机号作为唯一标识方便登录和查询 balance REAL DEFAULT 0.0 CHECK (balance 0), -- 账户余额不能为负 points INTEGER DEFAULT 0, -- 积分可用于兑换 level INTEGER DEFAULT 1, -- 会员等级关联折扣规则 remark TEXT, -- 备注如“过敏体质”、“偏好某发型师” created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) # 账户流水表至关重要每一笔资金变动都必须记录 cursor.execute( CREATE TABLE IF NOT EXISTS account_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, member_id INTEGER NOT NULL, type TEXT NOT NULL, -- charge(充值), consume(消费), refund(退款) amount REAL NOT NULL, -- 变动金额正负代表收入/支出 before_balance REAL NOT NULL, after_balance REAL NOT NULL, payment_method TEXT, -- cash, wechat, alipay, balance related_id INTEGER, -- 关联的消费订单ID或充值活动ID remark TEXT, -- 如“充值500送100”、“消费剪发一次” operator TEXT, -- 操作员 created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (member_id) REFERENCES member (id) ) ) # 消费订单表 cursor.execute( CREATE TABLE IF NOT EXISTS order ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT UNIQUE NOT NULL, -- 订单号可按规则生成 member_id INTEGER, total_amount REAL NOT NULL, -- 订单原总价 discount_amount REAL DEFAULT 0.0, -- 折扣金额 final_amount REAL NOT NULL, -- 实付金额 status TEXT DEFAULT completed, -- completed, refunded operator TEXT, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (member_id) REFERENCES member (id) ) ) # 订单项目明细表 cursor.execute( CREATE TABLE IF NOT EXISTS order_item ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, service_id INTEGER NOT NULL, quantity INTEGER DEFAULT 1, unit_price REAL NOT NULL, subtotal REAL NOT NULL, FOREIGN KEY (order_id) REFERENCES order (id), FOREIGN KEY (service_id) REFERENCES service (id) ) ) # 服务项目表 cursor.execute( CREATE TABLE IF NOT EXISTS service ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, price REAL NOT NULL, category TEXT, -- 剪发, 烫染, 护理 is_active INTEGER DEFAULT 1 ) ) self.conn.commit() # ... 后续的增删改查方法 ...设计要点解析member.phone设为UNIQUE手机号是比姓名更好的唯一标识也便于后续发送短信通知。account_log流水表是核心这是保证账目清晰的“铁证”。任何对member.balance的修改都必须同步插入一条流水记录记录变动前余额、变动金额、变动后余额。这样任何时候对账都有迹可循。订单与订单明细分离一个订单可能包含多个服务项目如剪发洗头。这种设计便于统计每个项目的销售情况。CHECK (balance 0)约束在数据库层面确保余额不为负这是业务规则的底线。4.2 消费收银流程的实现事务与并发控制收银是最高频、最核心的操作必须保证原子性和数据一致性。想象一下同时为同一个会员办理消费和充值如果处理不当余额就会错乱。# business.py - 核心业务逻辑 from models import Database import threading db Database() # 使用线程锁确保同一会员的账户操作串行化避免并发导致余额错误 member_locks {} def get_member_lock(member_id): 获取会员专属锁简易的并发控制 lock member_locks.get(member_id) if lock is None: lock threading.Lock() member_locks[member_id] lock return lock def consume(member_id, service_items, payment_methodbalance, operatoradmin): 会员消费扣款 :param member_id: 会员ID :param service_items: 列表如 [{service_id:1, quantity:1}, ...] :param payment_method: 支付方式 :param operator: 操作员 :return: (success, message, order_id) lock get_member_lock(member_id) with lock: # 对同一会员的账户操作加锁 cursor db.conn.cursor() try: # 1. 开启数据库事务 cursor.execute(BEGIN TRANSACTION) # 2. 查询会员当前余额和等级 cursor.execute(SELECT balance, level FROM member WHERE id?, (member_id,)) member cursor.fetchone() if not member: return False, 会员不存在, None current_balance member[balance] member_level member[level] # 3. 计算订单总价和折扣 total_amount 0.0 order_items [] for item in service_items: cursor.execute(SELECT price, name FROM service WHERE id? AND is_active1, (item[service_id],)) service cursor.fetchone() if not service: raise ValueError(f服务项目{item[service_id]}不存在或已下架) subtotal service[price] * item[quantity] total_amount subtotal order_items.append({ service_id: item[service_id], service_name: service[name], quantity: item[quantity], unit_price: service[price], subtotal: subtotal }) # 4. 根据会员等级计算折扣这里假设等级1无折扣等级2九折等级3八折 discount_rate {1: 1.0, 2: 0.9, 3: 0.8}.get(member_level, 1.0) discount_amount total_amount * (1 - discount_rate) final_amount total_amount - discount_amount final_amount round(final_amount, 2) # 保留两位小数 # 5. 余额支付校验 if payment_method balance: if current_balance final_amount: return False, f余额不足。当前余额{current_balance}元需支付{final_amount}元, None new_balance current_balance - final_amount else: # 现金或微信支付不扣余额 new_balance current_balance # 6. 生成订单号简易规则日期时间随机数 import time, random order_no time.strftime(%Y%m%d%H%M%S) str(random.randint(100, 999)) # 7. 插入订单主记录 cursor.execute( INSERT INTO order (order_no, member_id, total_amount, discount_amount, final_amount, operator) VALUES (?, ?, ?, ?, ?, ?) , (order_no, member_id, total_amount, discount_amount, final_amount, operator)) order_id cursor.lastrowid # 8. 插入订单明细 for item in order_items: cursor.execute( INSERT INTO order_item (order_id, service_id, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?) , (order_id, item[service_id], item[quantity], item[unit_price], item[subtotal])) # 9. 更新会员余额如果需要 if payment_method balance: cursor.execute(UPDATE member SET balance?, updated_timeCURRENT_TIMESTAMP WHERE id?, (new_balance, member_id)) # 10. 插入账户流水记录无论何种支付方式都记录消费行为 cursor.execute( INSERT INTO account_log (member_id, type, amount, before_balance, after_balance, payment_method, related_id, remark, operator) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , (member_id, consume, -final_amount, current_balance, new_balance, payment_method, order_id, f消费订单{order_no}, operator)) # 11. 提交事务 db.conn.commit() # 12. 返回成功结果 return True, f消费成功订单号{order_no}实付{final_amount}元余额{new_balance}元, order_id except Exception as e: # 发生任何异常回滚事务 db.conn.rollback() print(f消费操作失败: {e}) return False, f系统错误{str(e)}, None finally: cursor.close()关键点与避坑指南事务Transaction是生命线从BEGIN TRANSACTION到COMMIT之间的所有数据库操作是一个原子单元。要么全部成功要么全部回滚。这防止了更新了订单却没扣余额或者扣了余额却没生成订单这种“半吊子”状态。线程锁是必要的补充数据库事务保证了单个连接内的原子性但在多线程/多请求的Web服务器环境下两个几乎同时到来的、针对同一会员的请求比如一个消费一个充值可能被两个不同的线程/数据库连接处理。如果没有额外的锁机制它们可能同时读取到旧的余额然后分别计算并更新导致最终余额错误。member_locks字典实现了简单的“会员级锁”确保对同一会员账户的修改是串行的。流水记录不可或缺第10步的流水插入必须在事务内与余额更新同步。这是审计和排查问题的唯一依据。异常处理与回滚try...except...finally结构确保任何错误如服务项目不存在、余额不足、SQL语法错误都能被捕获并执行rollback将数据库恢复到操作前的状态避免脏数据。4.3 数据统计与看板用SQL挖掘经营信息数据统计不需要花哨的图表几个关键数字和列表就能说明问题。# stats.py - 数据统计 from models import Database from datetime import datetime, timedelta db Database() def get_today_stats(dateNone): 获取指定日期的经营概况默认今天 if date is None: date datetime.now().strftime(%Y-%m-%d) cursor db.conn.cursor() # 今日营业额已完成订单的实付金额总和 cursor.execute( SELECT COALESCE(SUM(final_amount), 0) as revenue FROM order WHERE DATE(created_time) ? AND status completed , (date,)) revenue cursor.fetchone()[revenue] # 今日新增会员 cursor.execute( SELECT COUNT(*) as new_members FROM member WHERE DATE(created_time) ? , (date,)) new_members cursor.fetchone()[new_members] # 今日最受欢迎服务项目按销售次数 cursor.execute( SELECT s.name, COUNT(oi.id) as sales_count FROM order_item oi JOIN service s ON oi.service_id s.id JOIN order o ON oi.order_id o.id WHERE DATE(o.created_time) ? AND o.status completed GROUP BY s.id ORDER BY sales_count DESC LIMIT 5 , (date,)) popular_services cursor.fetchall() # 会员总储值余额 cursor.execute(SELECT COALESCE(SUM(balance), 0) as total_balance FROM member) total_balance cursor.fetchone()[total_balance] cursor.close() return { date: date, revenue: revenue, new_members: new_members, popular_services: [dict(row) for row in popular_services], total_balance: total_balance } def get_member_consumption_rank(start_date, end_date): 获取一段时间内的会员消费排名 cursor db.conn.cursor() cursor.execute( SELECT m.name, m.phone, COUNT(o.id) as order_count, COALESCE(SUM(o.final_amount), 0) as total_spent FROM member m LEFT JOIN order o ON m.id o.member_id AND o.created_time BETWEEN ? AND ? AND o.status completed GROUP BY m.id ORDER BY total_spent DESC LIMIT 20 , (start_date, end_date)) rank_list cursor.fetchall() cursor.close() return [dict(row) for row in rank_list]这些统计结果可以通过Flask的路由暴露为JSON API供前端看板调用。SQL查询是这类统计的核心写得好的SQL能极大减轻后端逻辑的复杂度。5. 部署、运维与常见问题排查对于店主来说系统稳定、易维护和能快速解决问题比功能强大更重要。5.1 单机部署的详细步骤假设我们使用PyInstaller打包了一个Windows可执行程序。准备环境在一台专门用于收银的Windows电脑上最好是老旧但稳定的台式机创建一个专用文件夹例如D:\BarberShop。放置文件将打包好的BarberShop.exe主程序、config.ini配置文件、templates前端模板文件夹、static静态资源文件夹一起拷贝到该目录。首次运行双击BarberShop.exe。程序会自动在相同目录下创建data文件夹并在其中生成初始的barbershop.db数据库文件。一个命令行窗口会打开显示服务器运行日志例如* Running on http://127.0.0.1:5000。访问系统在这台电脑上打开浏览器如Chrome输入地址http://127.0.0.1:5000即可访问系统。为了店内其他设备如平板也能访问需要在config.ini中将主机改为0.0.0.0并设置一个店内局域网IP例如http://192.168.1.100:5000。设置开机自启为BarberShop.exe创建一个快捷方式并将其放入系统的“启动”文件夹shell:startup这样电脑一开机管理系统就会自动在后台运行。5.2 数据备份方案数据是生命线。必须建立可靠的备份机制。手动备份最简单的方法定期如每天打烊后将data文件夹整个复制到U盘或另一个硬盘。可以在桌面创建一个批处理文件backup.bat内容如下echo off set BACKUP_PATHE:\Backup\BarberShop set SOURCE_PATHD:\BarberShop\data if not exist %BACKUP_PATH% mkdir %BACKUP_PATH% xcopy /Y /E %SOURCE_PATH% %BACKUP_PATH%\%date:~0,4%%date:~5,2%%date:~8,2%\ echo 备份完成于 %date% %time% pause自动备份编写一个Python脚本利用shutil库定时拷贝数据库文件并压缩归档。可以使用Windows的“任务计划程序”来定时执行这个脚本。5.3 常见问题与故障排查实录在实际使用中你或店主很可能会遇到以下问题问题1打开浏览器访问地址显示“无法连接”或“拒绝访问”。排查步骤检查主程序是否正在运行。查看任务管理器是否有BarberShop.exe进程。检查命令行窗口显示的IP和端口是否正确。确认浏览器中输入的地址与之匹配。如果是局域网内其他设备无法访问检查电脑的防火墙设置是否阻止了5000端口的入站连接。需要在防火墙中为程序或端口添加允许规则。检查config.ini中host是否设置为0.0.0.0允许所有网络接口访问。问题2操作时提示“数据库错误”或“数据库被锁定”。原因分析SQLite在写入时会对数据库文件加锁。如果程序异常退出如直接关闭命令行窗口或者有多个程序实例同时尝试写入比如不小心双击了两次BarberShop.exe就可能出现锁冲突。解决方案首先确保只有一个程序实例在运行。关闭所有BarberShop.exe进程然后重新启动一个。如果问题依旧可能是数据库文件损坏或处于不一致状态。使用备份文件barbershop.db.bak进行恢复。在代码中确保数据库连接在使用后正确关闭或使用连接池管理。Flask-SQLAlchemy等ORM库能更好地处理连接问题。问题3会员余额显示不对。排查步骤核对流水这是最关键的步骤。在系统的“账户流水”页面筛选该会员逐条核对每一笔充值、消费记录。查看before_balance,amount,after_balance三列是否逻辑自洽前余额变动金额后余额。检查并发操作回忆是否在极短时间内对该会员进行了多次操作如快速连续消费。如果是可能是并发控制问题。检查代码中是否像我们之前那样对会员账户操作加了锁。检查手动修改是否有人直接通过第三方工具如DB Browser for SQLite打开过数据库并手动修改了member表的balance字段这绕过了系统的业务逻辑和流水记录是绝对禁止的。所有余额变动必须通过系统的业务接口进行。问题4系统运行越来越慢。原因与优化数据量增长会员和流水记录积累过多。SQLite在处理几十万条记录时简单查询依然很快但复杂联表统计可能会变慢。优化建议为常用查询字段加索引例如account_log表的member_id和created_timeorder表的member_id和created_time。# 可以在数据库初始化时创建索引 cursor.execute(CREATE INDEX IF NOT EXISTS idx_log_member_time ON account_log (member_id, created_time)) cursor.execute(CREATE INDEX IF NOT EXISTS idx_order_member_time ON order (member_id, created_time))定期归档历史数据对于超过一年的流水记录可以导出到单独的归档文件从主表中删除减轻主表压力。升级硬件最直接的方法将系统迁移到性能更好的电脑上或增加内存。开发这样一个系统最难的不是技术实现而是对业务细节的精准把握和持续的耐心打磨。每一个错误提示是否清晰每一个操作按钮的位置是否顺手报表的数字是否一眼就能看懂这些细节决定了店主是否愿意每天使用它。我的经验是在开发后期一定要花足够的时间坐在理发店里观察店主的真实操作流程记录下他们的每一个皱眉和每一次犹豫然后回头来优化你的设计。让工具去适应人而不是让人去适应工具这才是“极简”背后的真正哲学。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门