ARTICLE · 1150844
美甲美睫店管理系统 · 技术文档 案例
概述
1.1 设计目标
| 目标 | 实现方式 |
|---|---|
| 极简部署(5 分钟) | 纯 Python 依赖,无数据库服务、无消息中间件,python run.py 一键启动 |
| 断网可用 | 所有资源(CSS/JS/图片/数据库)本地化,不使用任何 CDN |
| 数据可迁移 | 单文件 SQLite + 本地图片目录,拷贝即迁移;内置一键备份/恢复 |
| 门店内部闭环 | 会员、卡项、开单收银、次卡核销、技师提成、耗材库存、报表、权限与日志 |
| 不做互联网能力 | 无小程序、无微信/短信推送、无第三方团购对接、无线上预约/商城、无多门店 |
1.2 技术选型
| 层次 | 选型 | 理由 |
|---|---|---|
| Web 框架 | Flask 3.1(蓝图 + Jinja2 服务端渲染) | 轻量、启动快、单机场景下运维成本最低 |
| 数据访问 | Python 标准库 sqlite3 + 手写薄封装 | 零 ORM 依赖;SQL 可控;便于直接查看 .db 排障 |
| 数据库 | SQLite(单文件 data/nail.db,WAL 默认模式) | 无需安装数据库;整机拷贝迁移 |
| 表格导出 | openpyxl | 生成 .xlsx,兼容 Excel/WPS |
| 认证 | Flask Session + Werkzeug generate_password_hash(PBKDF2) | 本地部署足够,无外部认证服务 |
| 前端 | Jinja2 模板 + 原生 JS + 手写 CSS | 无构建步骤、无 npm 依赖 |
| 测试 | pytest + Flask 测试客户端 | 覆盖核心资金与库存链路 |
2. 总体架构
2.1 分层
┌──────────────────────────────────────────────────────────┐
│ 浏览器(门店前台电脑,127.0.0.1:5000) │
└─────────────── HTTP(表单 / JSON) ──────────────────────┘
│
┌──────────────────────────────────────────────────────────┐
│ 表现层 templates/*.html + static/app.css │
├──────────────────────────────────────────────────────────┤
│ 路由层 app/*_bp(11 个蓝图:dashboard/members/cards/ │
│ items/booking/pos/commission/inventory/reports/ │
│ system/auth) │
├──────────────────────────────────────────────────────────┤
│ 横切层 auth.py(登录/权限/日志) utils.py(金额/单号/上传/ │
│ Excel) db.py(连接/查询辅助) │
├──────────────────────────────────────────────────────────┤
│ 业务层 services.py(卡资金、次卡核销、提成、库存、过期标记) │
├──────────────────────────────────────────────────────────┤
│ 数据层 SQLite:schema.sql 定义的 22 张表 │
└──────────────────────────────────────────────────────────┘
2.2 请求生命周期
Flask 接收请求 → 蓝图路由匹配;
@login_required校验会话,未登录重定向/login?next=...;@perm_required('x.y')校验角色权限,不足返回 403 页面;get_db()在应用上下文内取请求级连接(g.db),开启外键约束;路由函数解析表单 → 调用
services.py业务函数 → 同一连接内执行多次写操作;成功
commit(),业务校验失败rollback()并flash提示;log_operation()写入operation_logs;请求结束
teardown_appcontext→close_db()提交并关闭连接。
2.3 关键目录
| 路径 | 说明 |
|---|---|
app/__init__.py | 应用工厂 create_app();建表 + 首次种子数据;注册蓝图/上传路由/模板过滤器 |
app/schema.sql | 建表 DDL(CREATE TABLE IF NOT EXISTS,可平滑升级) |
app/seed.py | 角色权限矩阵、默认账号、默认项目/商品/工位/耗材/提成规则 |
app/services.py | 与资金、库存相关的单一事实来源,所有扣款/扣次/出入库都必须经过它 |
data/nail.db | 业务数据库(可用任意 SQLite 工具直接查看) |
data/uploads/{styles,members} | 款式图与会员历史款式照片(本地文件) |
data/backups/ | 一键备份产物 nail_backup_YYYYmmdd_HHMMSS.db |
启动系统.bat | Windows 一键启动(GBK 编码,见 8.1 说明) |
启动系统.ps1 | PowerShell 一键启动(UTF-8 with BOM,供 PS 5.1 正确读取中文) |
start.sh | Linux / macOS 一键启动 |
run.py | 服务入口(强制 UTF-8 输出、自动开浏览器、端口冲突友好提示) |
templates/macros/charts.html | 纯 SVG 图表宏(折线/环形/条形/分段条),无任何前端库依赖 |
app/dashboard.py | 工作台数据聚合 + 图表坐标计算 _area_geo() |
2.4 前端渲染约定(离线优先)
零外部依赖:不使用任何 CDN、npm 包或图表库;样式全部手写在
static/app.css。图表用服务端计算的 SVG:折线图的坐标、刻度、面积路径在
_area_geo()里算好,模板只做遍历输出;环形图用stroke-dasharray/dashoffset分段绘制;柱状/分段条同理。好处:无 JS 即可出图、断网可用、便于截图与自动化测试断言。左侧菜单用原生
<details>分组:无需 JS 即可折叠;服务端根据request.endpoint给分组打open与active(例:进入/members/时"客户中心"自动展开并高亮)。菜单与快捷入口按权限渲染:模板统一判断
current_user.perms,新增权限点只需在app/seed.py::ROLE_PERMS注册,无需改模板结构。图表数据口径:分类营收与项目热度采用"项目业绩口径"——现金结算取
order_items.amount,次卡核销取unit_price × quantity,与技师提成的业绩基数口径保持一致。
3. 数据模型
共 22 张表,可分为 6 组。
3.1 组织与权限
| 表 | 关键字段 | 说明 |
|---|---|---|
staff | username(唯一)、password_hash、name、role、is_technician、active | 员工/技师账号,技师由 is_technician=1 标记 |
operation_logs | staff_id、module、action、target_type、target_id、detail | 办卡/充值/退款/改订单/库存变动/转让等全部留痕 |
settings | key、value | 店名、耗材开关、仿真参数等键值配置 |
3.2 会员
| 表 | 关键字段 | 说明 |
|---|---|---|
members | name、phone、birthday、source、tags、nail_condition、lash_condition、allergy_note | 美甲/美睫专属档案与过敏备注 |
member_photos | member_id、filename、note | 历史款式照片,文件存 uploads/members/ |
visits | member_id、content、visit_date、next_date | 前台手动回访记录(无自动消息) |
3.3 卡项(核心)
| 表 | 关键字段 | 说明 |
|---|---|---|
cards | card_type(value/times/package/experience/voucher)、balance、bonus、total_times、used_times、scope、face_value、start_date、end_date、status | 一张表承载 5 类卡;status: active/frozen/transferred/expired/depleted |
card_balance_logs | action(recharge/refund/consume/adjust/freeze/unfreeze/transfer_in/transfer_out)、amount、bonus_amount、变动前后余额 | 每笔资金变动均有流水 |
card_usage_logs | card_id、times、remaining_after、order_id、technician_id | 每次核销一条记录,退款时写入负次数回退 |
设计要点:储值卡消费时先扣赠送金额、再扣本金(services.consume_balance),本金与赠送分开记账,便于核算真实负债;退款时调用 services.split_refund_by_consume() 读取该订单的消费流水,按当时的本金/赠送扣款比例原路退回,避免"赠送被吃掉、本金虚增"的账务错配。
3.4 项目与商品
| 表 | 关键字段 |
|---|---|
services | category(美甲/美睫/手足护理/卸甲卸睫/附加项)、price(原价)、member_price、duration、is_attach、active |
styles | name、tags、image_path(本地相对文件名)、remark |
products | name、spec、price、cost |
commission_rules | target_type(service/product)、target_id(唯一)、mode(fixed/percent)、value、active |
3.5 预约与工位
| 表 | 关键字段 |
|---|---|
workstations | name、kind(美甲工位/美睫工位)、active |
schedules | staff_id + work_date(唯一)、start_time、end_time、status(work/rest) |
appointments | member_id/guest_name、staff_id、workstation_id、start_time、duration、status(pending/checked_in/serving/completed/cancelled/rescheduled/no_show) |
appointment_items | appointment_id、service_id、service_name |
3.6 交易与库存
| 表 | 关键字段 | 说明 |
|---|---|---|
orders | order_no(唯一)、member_id、subtotal、discount、total、pay_method、balance_paid/cash_paid/custom_paid/voucher_paid、pay_card_id、voucher_card_id、status(pending/paid/refunded/free/void) | 订单永久保存 |
order_items | kind(service/product)、ref_id、unit_price、quantity、technician_id、settle_type(money/times)、card_id、usage_id、style_id、style_note、amount | 一行一个项目/商品/附加项 |
order_item_materials | order_item_id、item_id、quantity | 可选:开单关联耗材 |
commissions | order_id、order_item_id、technician_id、base_amount、mode、rate、amount、month、settle_type | 提成明细,按月汇总 |
refunds | order_id、amount、restore_balance、restore_times、reason | 退款登记 |
inventory_items | name、unit、stock、safe_stock、cost、supplier_id | 耗材 |
inventory_logs | action(in/out/check/loss/adjust/order)、quantity、stock_before、stock_after | 库存台账 |
suppliers | name、contact、phone | 供应商 |
4. 核心业务流程
4.1 开单结算(POST /pos/checkout)
解析明细行 → 校验(次卡可用性/剩余次数)→ 计算金额
├─ money_subtotal = Σ(现金结算行 单价×数量)
├─ times_value = Σ(次卡核销行 原价×数量) ← 只计业绩,不参与收款
├─ total = max(money_subtotal − 折扣, 0)
└─ 校验 收款组合 == total(误差 > 0.01 直接拒绝,事务回滚)
写入 orders → 写入 order_items → 实扣(余额/代金券)→ 次卡扣次(生成 usage)
→ 生成提成 → 可选耗材出库 → log_operation
收款方式:
balance(会员余额) /cash(现金) /custom(自定义,手工记录名称,不接支付接口) /voucher(代金券),可组合为mixed。挂单:
action=hold→status=pending,不扣款、不扣次、不计提成,后续/pos/orders/<id>/pay补结算。免单:
action=free→ 折扣等于小计、status=free、不计提成。
4.2 次卡核销与提成时点
办卡/充值不计业绩;只有在订单中核销(
settle_type='times')时才:used_times += 1(用完自动置depleted);写
card_usage_logs(含剩余次数、技师、订单号);按会员价作为业绩基数计提提成(该行
amount=0,不产生收款)。到期处理:任何打开卡列表的请求都会调用
mark_expired_cards()自动置expired;冻结/过期/转让的卡is_card_usable()返回 False,禁止核销。
4.3 退款(POST /pos/orders/<id>/refund)
回退该订单全部次卡核销次数(写负次数流水);
按原支付渠道退回:会员余额退回原储值卡(本金与赠送金额按原扣款比例拆分退回,见
split_refund_by_consume())、代金券退回面额;登记
refunds记录;冲减该订单全部提成(
amount=0,名称追加「已退款」);订单置
refunded并写操作日志。
4.4 提成计算(services.commission_for)
规则命中:
commission_rules(target_type, target_id),未命中返回 0;fixed:提成 = 固定金额;percent:提成 = 业绩基数 × 百分比 / 100;整单折扣分摊:现金结算行按
total / subtotal系数折算后计算,次卡核销行不打折;默认种子规则:服务项目 30%、附加项固定 8 元、零售商品 10%。
4.5 库存变动(services.change_stock)
统一入口:校验变动后库存不为负 → 更新 inventory_items.stock → 写 inventory_logs(含变动前后与单位成本)。出库类型:out 领用、loss 报损、order 开单消耗;check 盘点传入实际盘点数量,自动计算差额。
5. 权限模型
权限点(16 个):member.view、member.edit、card.manage、cashier、finance.recharge、finance.refund、inventory.view、inventory.edit、commission.view_all、commission.view_self、commission.setting、report.view、booking.view、booking.edit、staff.manage、system.backup、item.manage。
| 权限 | 老板 OWNER | 店长 MANAGER | 前台 RECEPTIONIST | 技师 TECHNICIAN |
|---|---|---|---|---|
| 会员查看 / 编辑 | ✅/✅ | ✅/✅ | ✅/✅ | ❌/❌ |
| 卡项管理、充值 | ✅ | ✅ | ✅ | ❌ |
| 开单收银、退款 | ✅ | ✅ | ✅ | ❌ |
| 项目商品管理 | ✅ | ✅ | ❌ | ❌ |
| 预约查看 / 编辑 | ✅ | ✅ | ✅ | 仅查看 |
| 个人提成查看 | ✅ | ✅ | ✅ | ✅(仅自己) |
| 全员提成 / 规则设置 | ✅ | ✅ | ❌ | ❌ |
| 库存查看 / 编辑 | ✅ | ✅ | 仅查看 | ❌ |
| 报表查看 | ✅ | ✅ | ❌ | ❌ |
| 员工管理、日志 | ✅ | ✅ | ❌ | ❌ |
| 备份恢复 | ✅ | ✅ | ❌ | ❌ |
技师账号(
t01/t02)登录后仅能看到自己的排班与业绩,无法看到门店财务总账。权限不足返回 403 页面(模板templates/errors/403.html)。
6. 接口清单
6.1 认证
| 方法 | 路径 | 说明 |
|---|---|---|
| GET/POST | /login | 登录(默认账号见 README) |
| GET | /logout | 退出 |
| GET/POST | /profile | 修改密码、查看自身权限 |
6.2 业务
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | / | 登录 | 工作台(今日营收、预约、挂单、到期次卡、库存预警) |
| GET | /members/ | member.view | 会员列表(手机号/姓名/标签检索) |
| GET/POST | /members/create | member.edit | 新建会员 |
| GET | /members/<id> | member.view | 会员详情(档案、卡、照片、消费、回访) |
| GET/POST | /members/<id>/edit | member.edit | 编辑档案 |
| POST | /members/<id>/photos | member.edit | 上传历史款式照片 |
| POST | /members/photo/<id>/delete | member.edit | 删除照片 |
| POST | /members/<id>/visits | member.edit | 记录回访 |
| GET | /members/lookup | member.view | JSON:会员速查(POS 用) |
| GET | /cards/ | member.view | 卡项总览(类型/状态筛选) |
| POST | /cards/create | card.manage | 办卡 / 发券 |
| GET | /cards/<id> | member.view | 卡明细(余额流水 + 核销记录) |
| POST | /cards/<id>/recharge | finance.recharge | 储值卡充值(本金 + 赠送) |
| POST | /cards/<id>/adjust | finance.recharge | 手工调账(留痕) |
| POST | /cards/<id>/freeze | card.manage | 冻结 / 解冻 |
| POST | /cards/<id>/transfer | card.manage | 卡转让(生成新卡,原卡作废) |
| POST | /cards/<id>/use-times | card.manage | 手工扣次核销 |
| GET | /cards/expiring | member.view | 次卡到期清单(30 天) |
| GET | /items/ | member.view | 项目库 / 款式图 / 零售商品 / 提成规则总览 |
| POST | /items/services/create|edit|toggle | item.manage | 服务项目维护(含提成规则) |
| POST | /items/styles/create、/items/styles/<id>/delete | item.manage | 款式图上传/删除 |
| POST | /items/products/create|edit | item.manage | 零售商品维护 |
| GET | /booking/ | booking.view | 当日预约与状态流转 |
| POST | /booking/create | booking.edit | 手动登记预约 |
| POST | /booking/<id>/status | booking.edit | 签到/服务中/完成/改期/爽约 |
| POST | /booking/<id>/to-order | cashier | 预约转挂单 |
| GET | /booking/stations | booking.view | 工位管理 |
| GET/POST | /booking/schedule、/booking/schedule/save | booking.view/edit | 周排班日历 |
| GET | /pos/ | cashier | 收银台 |
| GET | /pos/member-cards/<id> | cashier | JSON:会员可用卡(余额/次数/券) |
| POST | /pos/checkout | cashier | 开单结算 / 挂单 / 免单 |
| GET | /pos/orders | cashier | 订单查询 |
| GET | /pos/orders/<id> | cashier | 订单详情与操作 |
| GET | /pos/orders/<id>/receipt | cashier | 小票打印视图(window.print()) |
| GET/POST | /pos/orders/<id>/pay | cashier | 挂单补结算 |
| POST | /pos/orders/<id>/refund | finance.refund | 退款(自动回退次数与余额) |
| POST | /pos/orders/<id>/void | finance.refund | 挂单作废 |
| GET | /commission/ | commission.view_self | 技师业绩与提成明细 |
| GET | /commission/rules、POST /commission/rules/save | commission.setting | 提成规则批量设置 |
| GET | /commission/export | commission.view_self | 提成明细导出 Excel |
| GET | /inventory/ | inventory.view | 耗材、预警、供应商、台账 |
| POST | /inventory/create、/<id>/edit、/<id>/stock | inventory.edit | 新增/编辑/出入库/盘点/报损 |
| GET | /inventory/export、/inventory/logs/export | inventory.view | 盘点表 / 台账导出 |
| GET | /reports/ | report.view | 报表总览(日/月切换) |
| GET | /reports/export/<kind> | report.view | 导出:revenue/technician/member/hot/inventory/expiring |
| GET | /system/staff、POST create|edit|reset-pwd | staff.manage | 员工与角色 |
| GET | /system/logs | staff.manage | 操作日志 |
| GET/POST | /system/settings | staff.manage | 店名、耗材开关 |
| GET | /system/backup、POST /create、/restore/<name>、/upload | system.backup | 备份与恢复 |
| GET/POST | /system/simulation | report.view | 经营动态仿真 |
| GET | /uploads/<sub>/<filename> | 登录 | 本地图片访问 |
7. 关键实现细节
| 主题 | 做法 |
|---|---|
| 金额精度 | utils.money() 统一 round(x, 2);所有写库金额先过 to_float;收款校验容差 0.01 元 |
| 事务 | 资金/库存类操作在同一 g.db 连接内完成,业务异常 db.rollback() 并提示,绝不产生半截数据 |
| 并发 | SQLite 连接 timeout=15,开启 PRAGMA foreign_keys=ON;单店前台场景足够 |
| 时间 | 统一本地时间字符串 YYYY-MM-DD HH:MM:SS;月份用 YYYY-MM(commissions.month、utils.month_range) |
| 单号 | gen_no(prefix) = 前缀 + 时间戳 + 3 位随机数,订单号前缀 D |
| 图片上传 | 白名单扩展名(jpg/png/gif/webp/bmp)+ UUID 重命名,防覆盖与目录穿越 |
| Excel 导出 | openpyxl 内存工作簿 + send_file(as_attachment=True),无临时文件残留 |
| 备份恢复 | 使用 sqlite3.Connection.backup() 在线复制,避免 Windows 下数据库文件被占用无法覆盖的问题 |
| 过期标记 | services.mark_expired_cards() 在卡列表请求时批量刷新 status='expired' |
| 密码 | werkzeug.security.generate_password_hash(PBKDF2-SHA256);忘记密码可由老板重置为 123456 |
8. 部署与运维
8.1 启动
推荐:一键启动脚本(自动完成环境检测 → 虚拟环境 → 依赖安装 → 启动前自检 → 启动)
启动系统.bat::正常启动(自动打开浏览器)
启动系统.bat--no-browser::不自动打开浏览器
启动系统.bat--skip-venv::跳过.venv,直接用系统Python
启动系统.bat--selftest::先跑pytest通过后再启动
启动系统.bat--help
脚本流程与容错策略:
| 步骤 | 实现要点 |
|---|---|
| 1 检测 Python | where python / where py 兜底;取 sys.version_info 后按 主版本×10000+次版本×100 与 30900 比较;失败给出安装指引 |
| 2 虚拟环境 | 已存在 .venv 直接复用;创建失败自动回退系统 Python |
| 3 依赖 | python -c "import flask, openpyxl" 探测,缺什么才装;国内镜像失败自动换官方源 |
| 4 自检 | 检查 requirements.txt/run.py、netstat 探测端口并自动顺延(最多 10 个)、创建 data/ 子目录、识别是否已有 nail.db |
| 5 启动 | 设置 NAIL_PORT,由 run.py 打开浏览器并打印访问地址与账号 |
编码注意事项(实践踩坑)
.bat必须存为 GBK/ANSI,且不要在脚本里chcp 65001:UTF-8 批处理配合 chcp 会让 cmd 的文件指针按字节错位,中文行会被当作命令执行。
.ps1必须存为 UTF-8 with BOM:PowerShell 5.1 对无 BOM 文件按 ANSI(cp936) 解码,中文会变乱码并导致解析失败。
.bat换行统一 CRLF;.sh保持 LF。
备选:命令行启动
pip install -r requirements.txt
python run.py # 默认 5000 端口并自动打开浏览器
setNAIL_PORT=8080# Windows 换端口
setNAIL_NO_BROWSER=1# 不自动打开浏览器
setNAIL_DATA_DIR=D:\nail # 自定义数据目录(可放U盘/网盘同步目录)
run.py 会把 stdout/stderr 重新配置为 UTF-8,避免中文提示在 Windows 控制台/重定向时乱码。
8.2 数据迁移
在原机器「系统设置 → 备份恢复 → 立即备份」或点击下载
.db;新机器安装 Python + 依赖,放好代码;
上传备份文件 → 点击「恢复」;或把整个
data/目录拷贝到新机器覆盖。
8.3 日常维护建议
每天下班前执行一次本地备份(
data/backups/),重要时拷贝到 U 盘;定期在「系统设置 → 备份恢复」下载历史备份归档;
需要核对账务时,用任意 SQLite 工具直接打开
data/nail.db查询orders、card_balance_logs、refunds。
8.4 常见问题
| 现象 | 处理 |
|---|---|
| 浏览器提示"127.0.0.1 拒绝连接" | 后台服务未运行(不是页面错误)。重新双击 启动系统.bat,看到"启动成功"后刷新浏览器 |
| 端口被占用 | 启动脚本会自动顺延;也可手动设置 NAIL_PORT |
| 忘记密码 | 用 boss 登录 → 系统设置 → 员工 → 重置密码(重置为 123456) |
| 小票打印排版 | 小票页为 260px 窄版,window.print() 调用系统打印机,可选 58/80mm 热敏 |
| 想看 SQL | 所有建表语句集中在 app/schema.sql,可直接阅读/调整 |
| 批处理中文乱码/报错 | 确认 启动系统.bat 以 GBK 保存且未使用 chcp 65001(见 8.1) |
9. 测试
9.1 运行
python -m pytest tests -q# 36 passedtests/conftest.py 提供:临时数据目录的应用 fixture、login() 助手、boss/technician 已登录客户端、seed_order() 造单助手。
9.2 用例清单
| 文件 | 覆盖点 |
|---|---|
test_dashboard.py | 工作台快捷入口按权限显隐、空库时图表不报错、有订单后图表渲染真实数据、左侧分组菜单 |
test_auth.py | 未登录跳转、登录成败、技师越权 403、技师可看自己业绩、老板全模块可达、退出 |
test_members_cards.py | 会员新建/编辑、储值卡办卡与充值余额、冻结后不可用、次卡扣次、卡转让、到期清单 |
test_pos.py | 现金开单与提成计提(30% 与固定 8 元)、余额付款扣减、次卡核销开单(扣次 + 计提成 + 0 收款)、挂单→结算→提成、免单不计提成、收款不一致拒绝、退款回退余额与次数并冲减提成、订单详情与小票 |
test_inventory.py | 入库/出库/盘点/报损数量正确、库存不足被拒、安全库存预警、盘点表与台账导出、供应商 |
test_reports_system.py | 6 类报表导出、提成导出、备份与恢复一致性、仿真计算、员工新建与日志、耗材开关 |
test_booking.py | 预约登记→签到→转开单、排班保存、工位新增 |
9.3 测试中的断言要点
提成金额精确到分:纯色美甲 138 元 × 30% = 41.40,钻饰固定 8.00;
次卡核销订单
total = 0(顾客不付钱)但commissions有记录;退款后储值卡可用余额回到 1000、提成合计为 0。
9.4 端到端浏览器测试(Playwright)
# 1) 用独立数据目录启动服务(避免污染正式数据)
setNAIL_DATA_DIR=<项目目录>\data_demo
setNAIL_PORT=5057
python run.py
# 2) 另开命令行执行
setE2E_BASE=http://127.0.0.1:5057
setE2E_DB=data_demo/nail.db
python e2e_check.py
脚本 e2e_check.py 会驱动 Chromium 真实走完业务闭环(登录 → 建会员 → 上传照片 → 办卡充值 →办次卡 → 三种开单 → 退款 → 预约签到转单 → 挂单结算 → 库存 → 提成 → 报表导出 → 员工/日志/备份/仿真 →技师越权校验),随后直接查询 SQLite 核对金额(订单数、营收、储值本金/赠送、剩余次数、退款、提成合计、库存、日志条数),并把关键页面截图输出到 docs/screenshots/。脚本对 JS 控制台错误与 HTTP 5xx 采取零容忍(发现即失败退出)。
最新一次执行:53 步全部通过,0 失败,0 个 JS 错误。
10. 二次开发指南
| 需求 | 改动位置 |
|---|---|
| 新增服务项目分类 | app/seed.py::DEFAULT_SERVICES、app/items.py::SERVICE_CATEGORIES |
| 调整提成算法 | app/services.py::commission_for、app/pos.py::_generate_commissions |
| 增加支付方式 | app/pos.py::_check_payments(校验)与 _apply_payments(实扣),订单表加列后同步 schema.sql |
| 新增角色/权限 | app/seed.py::ROLE_PERMS / ROLE_LABELS / PERM_LABELS,路由加 @perm_required(...) |
| 增加一张业务表 | app/schema.sql(CREATE TABLE IF NOT EXISTS)+ 新蓝图或现有蓝图路由 |
| 接 ESC/POS 指令打印机 | 替换 templates/pos/receipt.html 的 window.print() 为本地打印服务调用(需另装打印代理) |
| 多店/连锁 | 当前为单店单机架构,需引入服务端数据库与门店维度字段,超出本版范围 |
代码规范约定
所有涉及钱/库存/次数的写操作必须经过
app/services.py,禁止在路由里直接 UPDATE 余额;新增写操作必须调用
log_operation(module, action, target_type, target_id, detail);金额统一
money();日期统一db.now()/utils.today();页面统一继承
templates/base.html,左侧菜单按current_user.perms自动显隐。
2. 登录与界面总览
2.1 登录

默认账号(密码均为 123456,登录后请及时修改):
boss | |
manager | |
front | |
t01t02 |
2.2 工作台(首页)

工作台从上到下依次是:快捷操作 → 今日经营数据 → 数据图表 → 待办提醒 → 今日预约。
① 快捷操作:常用的 8 个入口一点即达,不用在菜单里找。
快捷入口与左侧菜单都会按账号权限自动显示:例如技师登录看不到"经营报表",前台登录看不到"员工与权限"。
② 今日经营数据(8 个卡片)
今日营收、今日单量 / 挂单数、今日充值、次卡核销次数; 会员总数 / 今日新增、储值余额总额(本金 + 赠送)、未核销剩余次数、本月营收。
③ 数据图表(全部为本地绘制,断网也能看)
分类营收按项目业绩口径统计:现金结算按实收金额,次卡核销按项目会员价计入,
所以环形图中心的"业绩合计"可能略高于当天实际收款(次卡核销当下不产生收款)。
④ 待办与提醒
待结算挂单、次卡即将到期(30 天)、库存预警三张提醒卡; 今日预约列表,可直接点"处理"跳到预约页做签到/开单。
2.3 左侧菜单(分组子菜单)
左侧菜单按业务分成四组,点组名可折叠/展开,当前页面所在分组会自动展开并高亮:
左侧菜单即为全部功能入口,菜单会按当前账号的权限自动显隐。
3. 会员建档与会员查询
3.1 新建会员
菜单「会员档案」→ 右上角「+ 新建会员」,填写姓名、手机号、生日、来源、标签,以及美甲美睫专属档案(指甲/睫毛情况、过敏备注)。

3.2 上传历史款式照片
在会员详情页「历史款式照片」区域选择图片、填写备注后点「上传」。图片保存在本机 data/uploads/members/,开单做款前可随时调阅客户历史款式与过敏信息。

3.3 会员查询
顶部搜索框支持手机号 / 姓名 / 标签模糊检索; 列表直接显示持卡数量与累计消费; 点姓名进入详情,可查看全部历史消费、卡内余额、剩余次数、款式照片、回访记录。 
3.4 客户回访记录
会员详情页底部「客户回访记录」→ 填写回访日期、内容与下次回访日期 → 「记录回访」。纯手动录入,无自动短信/微信推送(本系统不依赖外网)。
4. 卡项管理:储值卡 / 次卡 / 券
4.1 办卡
三种入口,效果一致:
会员详情页 →「会员卡项」→「办理新卡 / 发券」; 菜单「卡项管理」→ 顶部办卡表单(先选会员); 开单收银时若发现客户没有卡,可先去卡项管理补办。
卡类型说明: