简介:面向美容院、水疗会所等门店经营场景的会员管理系统源码,同时覆盖电脑端管理后台和微信端,方便门店管理者直接部署使用,也适合后端开发人员作为权限设计与业务功能实现的参考项目。系统权限可细分到每个功能节点,会员、套餐、订单等常用数据的增删改查一应俱全,并内置电子表格导出能力,便于基于会员信息生成经营报表。压缩包共1601个文件,整体体积约23.4MB,文件类型以PHP、HTML、JavaScript、CSS等典型网页开发文件为主,同时包含大量GIF、PNG、JPG图片素材与配置文件,并附有安装引导和数据库SQL脚本,从环境配置到页面展示环节完整。前台页面与后台逻辑分区明确,微信端、公共函数、模板样式等模块相对独立,二次开发时容易定位。目前已有3256人学习下载,既可以直接部署用于门店日常管理,也可以按模块阅读源码,深入理解权限细分到功能按钮、基础增删改查和电子表格导出功能的完整实现,整体是可落地、可扩展的参考资源。
1. 美容院会员管理系统:这套源码能解决什么问题
先直接说结论:美容院会员管理系统源码,不是让你从零搭平台,而是把门店收银和微信端会员自助之间最麻烦的那层业务已经拆好了。上一轮交付的微信小程序源码工程,功能做得再全,也没法像网页那样在电脑浏览器里直接打开,必须在微信开发者工具里编译运行,这是很多人拿到包后的第一个误解。这套源码管的事很具体:后台维护会员档案、开卡、充值、记消耗、算员工提成;微信端让顾客自己查余额、看消费记录、接收到期和续卡提醒。适合单店美容院和皮肤管理工作室直接上线,也适合想拿一套完整业务骨架做课程设计或二次开发的人来参考。
2. 后台与微信端的整体拆解:六个模块和一条数据主线
2.1 后台的六个模块,对应店铺的真实动作
拿到这类源码包,第一件事不是看代码,而是把后台菜单翻一遍。美容院会员管理系统基本绕不开六个模块:会员管理、卡片管理、充值赠送、消费记录、员工提成、系统设置。这个分布不是凑的,拆开看每一块都对应店里每天发生的动作。前台开卡对应会员管理和卡片管理,顾客充值对应充值赠送,项目做完划卡扣费对应消费记录,月底结算对应员工提成,店铺名称和微信支付参数归系统设置。
模块之间的关系用一张表说清楚,这也是后续加功能时判断数据往哪张表放的依据:
| 后台模块 | 对应主表 | 核心字段 |
|---|---|---|
| 会员管理 | member | mobile 唯一索引、name、birthday、source |
| 卡片管理 | card_type / member_card | balance、total_count、used_count |
| 充值赠送 | recharge_rule / balance_log | rule_name、gift_amount、min_amount |
| 消费记录 | consumption_log | before_balance、after_balance、card_id |
| 员工提成 | staff / commission_log | rate、base_amount、order_id |
| 系统设置 | setting | store_name、appid、mch_id |
顺着这张表能看到一条主线:member 表是根,member_card 挂在它下面,balance_log 和 consumption_log 记录每张卡的进出账,commission_log 再引用消费记录算提成。所以这套系统里最敏感的表就是前四张。源码里如果余额变化直接 update member_card 而不写流水,那要警惕,一旦出纠纷,没有流水根本没法回溯。
2.2 微信端只承担三件事:自助查询、预约与提醒
微信端在美容院场景里不做收银,它只干三件没有收银员参与的事:查余额与消费明细、提交预约、接收到期提醒。后台是写端,微信端是读端加少量写操作。页面结构因此很轻,首页展示店铺和技师,会员页显示持有的卡和余额,明细页列消费记录,预约页提交时间,我的页面绑定手机号。
微信端与后端通信走 JSON 接口,小程序用 wx.request 请求。调试期可以勾选“不校验合法域名”,真要上线就必须在微信公众平台配置服务器域名,并且要求 HTTPS。接口层一般收口在一个 api.js 文件里,我习惯先看这个文件,确认接口路由和 baseUrl 有没有集中管理。
// 小程序端请求封装,常见源码包中 utils/request.js 的通用写法 const BASE_URL = 'https://api.example.com'; // 部署后替换成自己的域名 function request(path, data = {}, method = 'POST') { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json' }, success(res) { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data); } else { reject(res.data.message || '请求失败'); } }, fail(err) { reject(err.errMsg); } }); }); }这里的前后端约定是 code 为 0 表示业务成功,非 0 为业务失败,网络层错误走 fail。BASE_URL 在一个文件里统一维护,测试环境切正式环境只需要改这一处。注意 res.data.code 的判断不能省——有些后端在业务异常时也返回 HTTP 200,只看 statusCode 会漏掉“卡已过期”这类业务错误。
2.3 一次开卡背后的数据链路:三张表必须同时成功
开卡动作看起来是写一条记录,实际牵动三张表。以典型 PHP 后端为例,流程是:往 member 表插入会员档案,拿到自增主键 member_id;往 member_card 插入卡实例,卡号按规则生成,余额初始为 0;最后往 balance_log 写一条初始流水,比如“开卡赠送 0 元”或“首次充值 1000 元”。这三步必须在同一个事务里,否则中途报错会出现“人有卡无”的脏数据。
// 一次开卡的事务边界:会员档案 + 卡实例 + 初始流水 $pdo->beginTransaction(); try { $stmt = $pdo->prepare( "INSERT INTO member (name, mobile, store_id, created_at) VALUES (:name, :mobile, :store_id, NOW())" ); $stmt->execute([ ':name' => $name, ':mobile' => $mobile, ':store_id' => $storeId, ]); $memberId = $pdo->lastInsertId(); $cardNo = 'C' . date('Ymd') . rand(1000, 9999); $stmt = $pdo->prepare( "INSERT INTO member_card (member_id, card_type_id, card_no, balance, created_at) VALUES (:mid, :tid, :cno, 0, NOW())" ); $stmt->execute([':mid' => $memberId, ':tid' => $cardTypeId, ':cno' => $cardNo]); $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); throw $e; }这个事务的关键点有两个。第一,member_id 来自 lastInsertId,不能手动拼一个数字,否则主外键分叉,会员档案和卡片对不上。第二,card_no 用 rand(1000, 9999) 生成,单店低并发没问题,多店并发会撞号,我一般建议加上数据库唯一索引兜底,或者换成 uniqid('C', true)。
参数说明:store_id 在单店场景固定为 1,多店连锁时引入 store 表,把员工、卡和消费记录都挂到店铺维度。card_type_id 决定卡的类型,储值卡的 balance 是金额,次卡的 balance 通常存剩余次数,年卡靠 expire_at 控制有效期。类型之间不能混用逻辑,这也是卡片类型表必须单独维护的原因。消费流水同理,每条记录都要保存操作前后的余额,方便后续对账和纠纷处理。
3. 开卡、充值、计次的落地实现:让账目从进店到离店闭环
3.1 储值卡、次卡、年卡、体验卡:字段设计决定后期改不改表
美容院会员卡形态比健身房更杂,常见就有四种:储值卡按金额扣,次卡按次数扣,年卡按有效期,体验卡用来拉新。源码里如果让它们共用一张 member_card 表,就必须靠 card_type 区分。我拆过的包多数是这样设计的:
| 卡类型 | 关键字段 | 扣费逻辑 | 过期逻辑 |
|---|---|---|---|
| 储值卡 | balance 金额余额 | 消费时扣 balance | 一般不过期 |
| 次卡 | total_count / used_count | 每次 used_count+1 | 可设 expire_at |
| 年卡 | start_at / expire_at | 不扣次数不扣钱 | 到期自动停用 |
| 体验卡 | 折扣率字段 | 结算按折扣计算 | 过期或限用一次 |
字段确定后,后台“开卡”页面就是在组一条记录:选会员、选卡类型、填金额或次数、选赠品或折扣、写备注。界面再怎么变,落到后端就是一次事务,复用的是 2.3 节那套逻辑。
提示:card_type 表尽量保留一个 is_deleted 字段,下架旧卡种时做软删除,避免历史流水外键断掉。
3.2 充 1000 送 200 的规则,为什么必须放在后端计算
充值赠送是美容院用得最多的营销,也是最容易翻车的地方。常见错误是前端页面写死规则,比如 [{“amount”:1000,“gift”:200},{“amount”:3000,“gift”:800}],用户输入金额后前端算好到账数提交。这种写法有两个问题:第一,规则写在页面里,店长调整活动就得改代码发版本;第二,前端计算的金额可以被构造,直接把到账金额改成 5000 提交,后端一旦照单全收就亏了。正确做法是后端保存规则,前端只提交实付金额,赠送由服务端计算。
// 充值规则判断:前端只传实付金额,赠送金额由后端计算 function calcGift($payAmount, $rules) { $gift = 0; foreach ($rules as $rule) { if ($payAmount >= $rule['min_amount']) { $gift = $rule['gift_amount']; // 命中当前最高档,继续循环匹配更大档位 } } return $gift; }这里要强调的是,规则表里的 min_amount 和 gift_amount 一定要能在后台维护,而不是写死在代码里。我见过一些源码把赠费档位放进常量,店长想改活动只能找人来改文件,小程序端改一次还得等审核,半天就耗进去了。把规则挪到数据库后,后台改完接口即时生效,微信端不用发版。
3.3 次卡扣减与并发:一个人同时划两次卡会出什么问题
次卡扣减的坑比储值卡更隐蔽。储值卡更新余额时用 set balance = balance - 100 这种 SQL,天然防并发;次卡如果先读 used_count,算好新值再写回,两个请求同时进来,都读到 used_count = 1,各自写成 2,实际只扣了一次,卡却显示少了两次。
-- 正确做法:用条件更新替代“先查后改” UPDATE member_card SET used_count = used_count + 1 WHERE id = :cardId AND used_count < total_count AND (expire_at IS NULL OR expire_at > NOW()); -- 受影响行数为 0 时,表示次数已用完或卡已过期这段 SQL 的精髓在 WHERE 条件。used_count < total_count 保证不会扣成负数,expire_at 判断保证过期卡不能继续划。后端拿到影响行数为 0 时,应该返回“次数不足”或“卡已过期”,而不是直接提示成功。再把扣次数和写消费记录包在同一个事务里,就能避免超扣和流水缺失两边对不上的情况。这里的并发问题不是玄学,就是锁和条件更新用得不彻底。
4. 微信端常见问题排查:登录、白屏与回调的四次翻车
4.1 登录接口报 40029:code 换 session_key 的时效问题
现象:小程序端 wx.login 拿到 code,传给后端调微信接口换 openid 和 session_key,返回错误码 40029,提示 code 无效。真机偶发,开发者工具里正常。
原因:code 是一次性的,有效期五分钟且只能用一次。只要前端对同一个 code 发起两次换 openid 的请求,第二次必然报 40029。另一种常见情况是前端把 code 存到一个全局变量里反复使用,页面刷新后没有重新调 wx.login。
解决:前端每次登录都重新执行 wx.login 拿新 code,后端不要缓存 code。正确流程是:wx.login 拿 code → 后端换 openid → 根据 openid 查 member 表拿会员信息 → 签发自有 token 返回前端。token 有效期建议设一周,太久的话顾客换手机后登录态容易混乱。
4.2 开发者工具正常、真机白屏:ES6 转换与基础库版本
现象:源码在开发者工具里预览一切正常,上传体验版后真机打开白屏,控制台报某个语法错误或找不到 xxx。
原因:两个问题叠加。第一,开发者工具默认开启“ES6 转 ES5”,掩盖了源码里不兼容的箭头函数或模板字符串;第二,真机基础库版本过低,Promise 或某些 API 不可用。
解决:打开项目详情,把“ES6 转 ES5”先关掉重新编译,看哪里报错再逐行改成兼容写法。同时把最低基础库版本设为 2.10.0 以上,老手机上版本过低时优先提示用户升级微信,而不是堆 polyfill。另外注意,有些源码依赖的地图、支付、订阅消息组件是插件形式,插件没在 app.json 里声明,真机也会白屏。
提示:真机白屏时优先打开 vConsole 面板,报错信息比逐行读代码更能定位问题。
4.3 图片上传失败:合法域名和临时文件路径一起查
现象:微信端上传头像或消费凭证,开发者工具里选图上传成功,真机报 uploadFile:fail。
原因:大概率两类。第一,后台接口域名没有配进小程序后台的 uploadFile 合法域名列表;第二,图片临时路径被缓存,第二次选同一张照片时用的还是旧路径。
解决:先在微信公众平台的“开发管理”里把 uploadFile 合法域名和 request 合法域名都加上。临时文件路径不要缓存,每次选图后都拿 res.tempFilePaths 的最新值。后端接收文件时,PHP 常见的坑是服务器 upload_tmp_dir 权限不够,导致 move_uploaded_file 返回 false,接口报成功但图片没落库。查日志才能发现权限问题。
4.4 支付回调验签失败:金额单位、空参数与证书路径
现象:微信端下单成功,顾客付款正常,但后台订单状态不更新,日志里出现签名错误。
原因:支付回调验签有三个常见原因叠在一起。第一,金额单位错,前端传元,后端按分处理,签名串和回调内容不一致;第二,回调数据里没有过滤空值,把空参数也拼进签名串;第三,源码里带的证书路径是作者本机的绝对路径,部署后找不到文件。
解决:金额内部统一用“分”做单位,出参给前端时才转成元。验签时按微信官方规则,过滤空值和 sign 字段,再按字典序拼接。证书路径不要写绝对路径,改成相对于项目根目录的路径,部署时用软链或环境变量注入。商家号 mch_id、API 密钥和证书也必须替换成自己的,这不是可选项,是上线前置条件。
5. 部署与初始化:把源码包变成能跑起来的店铺后台
5.1 环境与目录结构:先分清后端工程和小程序工程
源码包通常包含两个工程:管理后台和微信小程序。解压后先看根目录有没有 README,没有也不慌,用目录结构判断。一般会有 backend 或 api 目录放后端,miniapp 或 weapp 目录放小程序。管理后台可能是接口项目加独立前端页面,也可能是一个完整网页项目。我习惯先在根目录搜 config 关键字,定位数据库配置文件和小程序接口配置文件,这是整个部署地图的起点。
后端常见 ThinkPHP 或类似 MVC 框架,入口 index.php 在 public 下,application 或 app 目录放控制器和模型,config 目录放数据库与微信配置。小程序端有 project.config.json 标识工具版本,app.js 里调登录,utils/request.js 管网络请求。不要一上来就改业务代码,先把两个工程各自跑通,再谈功能调整。
5.2 数据库初始化与配置文件:SQL 导入和账号修改
初始化数据库这步,顺序很关键。很多包自带 SQL 文件,放在根目录的 sql 或 database 目录里。先建库,再导入,然后改配置文件。以后端用 PHP 为例,数据库配置通常在 config/database.php 或 .env 文件里,核心就是主机、端口、库名、账号、密码五项。常见错误是只改库名不改账号密码,连不上后去查业务代码,白折腾半小时。
# 初始化数据库的常见动作:先导入表结构,再导入初始数据 mysql -uroot -p < ./sql/schema.sql mysql -uroot -p < ./sql/init_data.sqlschema.sql 建表结构,init_data.sql 塞默认的卡类型、充值规则和初始管理员账号。导入后先 SELECT 一下 member 表有没有数据,再查 admin 表确认管理员账号存在,避免登录时报错。
注意:init_data.sql 里的管理员密码通常是 123456 明文,登录进去第一件事改成 8 位以上混合密码。
接口地址配置决定小程序能不能连上后端。小程序端 config 或 app.js 里通常有一个 baseUrl,测试时用 http://localhost 不行,手机访问不到电脑本机,要先用局域网 IP。真机预览时把 baseUrl 改成电脑的局域网地址,并在开发阶段勾选“不校验合法域名”,真正上线再把域名换成 HTTPS 正式地址。
5.3 小程序侧参数替换:AppID、服务器域名与体验版
小程序跑起来之前有三个位置必须替换,少任何一个都会卡在登录或支付环节。第一是 project.config.json 里的 appid,源码包自带的是作者测试号,不替换会在开发者工具里提示 appid 不匹配。第二是后端配置里的 appid 和 secret,必须换成自己的小程序账号。第三是服务器域名,走体验版时 request、uploadFile 和 downloadFile 的合法域名都要配,不能只配 request。
{ "appid": "wx你的小程序AppID", "compileType": "miniprogram", "setting": { "urlCheck": false } }urlCheck 在开发者工具里设为 false 可以跳过域名校验,方便本地调试,但发布体验版前要改回 true,否则真机请求会被微信拦截。appid 替换后记得清缓存重新编译,我遇到过替换后白屏,最后发现是开发者工具缓存里还在用旧 appid。
5.4 上线前检查清单:九个动作过一遍再交付
我从“能跑”到“敢用”的检查项列一下,部署完一项项过:数据库自动备份有没有配置;管理员初始密码是否已改;小程序 appid 和 secret 是否换成正式账号;后端域名是不是 HTTPS 且证书未过期;request、uploadFile、downloadFile 三组域名是否都配置;支付商户号与后端配置是否一致;员工账号是否按最小权限分配;充值、退款等敏感接口有没有登录态校验;消费流水表是否开启定期归档。这些动作看起来琐碎,但每一件都对应一次真实事故。给合作店铺做验收时,我至少要看到三条线上日志才敢说系统能交付。
6. 批量导入老会员:Excel 数据补录的最后一个坑
6.1 老会员数据先进临时表,再进正式表
换新系统最现实的问题是老店上百个会员不能靠前台手敲。常见做法是把 Excel 整理成三列:手机号、姓名、卡余额,导入临时表,再通过 SQL 合并进正式表。切忌直接在正式表上逐条执行 INSERT,Excel 里一行手机号重复、一行格式错误,就会中断整个导入过程。
-- 先把 Excel 数据落进临时表 member_import CREATE TEMPORARY TABLE member_import ( mobile VARCHAR(11) PRIMARY KEY, name VARCHAR(50), balance DECIMAL(10,2) ); -- 用 INSERT ... SELECT 把老会员并进正式表 INSERT INTO member (name, mobile, store_id, created_at) SELECT name, mobile, 1, NOW() FROM member_import ON DUPLICATE KEY UPDATE name = VALUES(name);临时表有两个作用:一是让 Excel 的脏数据在临时表阶段被过滤,二是用 ON DUPLICATE KEY UPDATE 解决手机号重复问题,重复时只更新姓名不新增档案。再补一步,把老会员的余额以初始流水写进 balance_log,否则前台看到的余额和系统账本对不上。卡类型字段按老店实际情况填写,没有就归到储值卡,后续再做调整。
6.2 导入后的三个对账点,少一个都会出错
导入不是跑完 SQL 就结束,我习惯再核三遍。第一遍,count 临时表行数要和正式表新增行数相等;第二遍,抽样几个会员,核对手机号、姓名和余额三要素;第三遍,跑一遍 SUM(balance) 对比 Excel 手工表的总额,差一分钱都要查出原因再继续。从那以后,我每次给店铺部署这套源码,导入老会员数据都会强制走一遍临时表加三道对账的流程,宁可多花十分钟,也不让旧数据把新账本污染掉。希望帮到你。
本文还有配套的精品资源,点击获取