简介:一套基于JavaScript的家政SaaS平台设计源码,面向Web前端开发者、家政服务创业者以及SaaS产品方向的学习者,旨在帮助解决家政服务场景中线上预约、服务管理、订单流转与后台配置等环节重复开发、效率不高的问题。项目采用JavaScript与React技术栈,以组件化、模块化方式组织业务逻辑,配合LESS样式表与JSON数据配置,覆盖了从用户端信息展示到运营端管理操作的前端完整界面。压缩包共包含206个文件,大小约1.22MB,其中JavaScript脚本118个、JSX组件52个、LESS样式表15个,另有PNG图片、JSON数据、Markdown文档等辅助内容,结构清晰,便于快速定位和学习。目前已有301人学习下载。源码内还提供了完善的工程化配置,适合作为家政SaaS平台前端架构、组件拆分、权限菜单及响应式交互设计的参考;深入研究后,可以掌握React项目从环境搭建、接口联调、状态管理到页面渲染的完整链路,也可直接作为毕业设计或商业原型的基础版本进行二次开发。
1. 基于Javascript的家政SaaS平台设计源码:为什么复杂度藏在业务约束里
家政SaaS看起来是一个“派单+记账”的后台,但真正落地时会发现,复杂度根本不在派单算法,而在十几张业务表之间的状态约束。一次保洁订单从创建、改期、换人到完成后按小时结算,中间任意一步都能被用户或服务人员触发,状态转移稍不严谨就会出现“服务已完成但财务单还挂着”的脏数据。
这篇文章讨论的是如何把这样一个平台的设计源码落到 JavaScript 技术栈上:前端 H5 工作台、管理后台、Node.js 服务端脚本都统一在 JavaScript 领域模型里。这里的“设计源码”不是某个开源仓库的名字,而是指以表结构、状态机、结算规则为核心的一套可运行代码设计。适合正在做 SaaS 产品设计、或准备接手家政行业独立开发的全栈工程师阅读,新手也能按章节顺序把最小可跑通的模块搭出来。
2. 多租户与数据字典:从服务项主数据开始的家政SaaS设计源码
家政 SaaS 通常同时服务多个城市、多个加盟门店,每个门店有自己的服务项目和定价,但底层服务项(日常保洁、深度保洁、开荒保洁)又是同一套。设计源码时第一件事就是把“平台统一主数据”和“租户个性数据”分开,否则后续每个月对账都会返工。
2.1 多租户隔离选型:共享表加tenant_id时最难的不是建表
常见做法有三种:独立数据库、共享库独立Schema、共享表加租户字段。家政订单体量不大但租户数量多,独立库的运维成本偏高,独立Schema在 MySQL 下又不利于跨租户统计。多数团队最终走共享表加 tenant_id 的路线,但这条路线最难的不是建表,而是防止 SQL 漏掉租户条件。
| 方案 | 隔离性 | 统计便利性 | 适合阶段 |
|---|---|---|---|
| 独立数据库 | 最强 | 需跨库聚合 | 大客户独占 |
| 共享库独立Schema | 中 | 按库聚合 | 中大型加盟商 |
| 共享表加tenant_id | 弱 | 一条SQL全看 | 平台起步期 |
为了防止串数据,业务层必须统一封装数据访问入口。所有查询先注入租户条件,不要在每个 Controller 里手工写 WHERE。遇到报表需求时,走独立的只读从库,避免定时任务把主库的租户索引打满。
2.2 服务项主数据与城市定价的JavaScript描述
服务项主数据要稳定。一次“日常保洁”的 ID 在平台里要永久有效,改名只能改展示名,不能改 ID。城市定价可以按“基础价+差分”方式存储,例如北京日常保洁 54 元/小时,周末上浮到 68 元/小时,这个差分规则用 JavaScript 对象描述很直观。
// 城市价格规则:基础价 + 时段差分,金额统一以“分”存储 const baseRule = { serviceId: 'P001', // 服务项ID,全局唯一 cityCode: '110100', // 城市编码 unitPrice: 5400, // 日常保洁基础价,单位分/小时 currency: 'CNY' }; const weekendDiff = { unitPrice: 6800, // 周末价,覆盖基础价 enabled: true }; // 使用对象展开做合并,而不是 Object.assign 直接覆盖 const merged = { ...baseRule, ...weekendDiff, priceVersion: '2025-06-01' };参数说明:unitPrice 以分为单位是为了避开 JavaScript 浮点数误差,订单金额、佣金、退款全部走整数运算;weekendDiff 只包含需要变化的字段,这种“差分合并”方式让不同城市能在同一份配置里继承基础服务项。使用对象展开合并时要注意,如果 baseRule 里存在嵌套对象,展开是浅拷贝,嵌套层仍可能互相影响。
2.3 字典版本化:下拉联动与CDN失效的顺序
服务项或价格调整后,最怕用户端还拿着旧字典提交订单。设计源码时需要给数据字典加版本号,并在前端发布时把版本号写进资源路径。比如把版本号拼到 CDN 目录上,旧版本缓存到期后自动回源。
前端 H5 页面在工作台里做下拉联动时,通常先拉/api/dict/currentVersion拿到版本号,再决定是否刷新本地缓存。使用 HBuilderX 打包发行时,需要在项目配置里把 html、css、javascript 的压缩选项打开,并将资源引用改为带版本号的绝对路径,这样字典更新与新包发布能保持同一次操作完成,避免新旧页面混用两套价格。
3. 服务人员与订单调度:家政SaaS设计源码中的状态机与JavaScript实现
家政平台的核心资产是服务人员。这一章围绕服务人员档案、技能标签、订单拆分和状态机展开,重点说明为什么订单和工单必须拆成两张表。
3.1 服务人员表与技能标签的存储取舍
服务人员的核心字段包括姓名、手机号、城市、服务等级、技能标签和当前状态。手机号和姓名属于个人敏感信息,入库前要用服务端加密算法处理,字段类型用 VARBINARY 保存密文,避免数据库泄露直接导致实名信息外流。
CREATE TABLE service_provider ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id VARCHAR(32) NOT NULL, provider_no VARCHAR(32) NOT NULL UNIQUE, real_name_cipher VARBINARY(256) NOT NULL, mobile_cipher VARBINARY(256) NOT NULL, skill_tags JSON NOT NULL, level TINYINT DEFAULT 1, city_code VARCHAR(16) NOT NULL, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_tenant_city (tenant_id, city_code) ) DEFAULT CHARSET=utf8mb4;参数说明:skill_tags 用 JSON 存储技能数组,例如["日常保洁","深度保洁","擦窗"]。MySQL 的 JSON 类型支持函数索引,可以配合 MEMBER OF 做快速过滤,但整体上 JSON 字段只适合低频筛选,高频调度查询还是建议拆一张 provider_skill 关联表。level 表示服务等级,影响派单时的权重;status 表示是否可接单,请假、离职都通过这个字段控制。
3.2 订单、工单、结算单一拆三:家政行业的订单修改高频
家政订单一定会被多次修改,用户可能改期、换人、加时,而服务人员看到的工单则是从订单拆分出来的具体执行任务。如果订单和工单混在一张表里,每改一次人员都要把订单维度所有字段复制一遍,极易产生不一致。
设计源码时建议拆成三个实体。订单表保存客户服务需求,工单表保存服务人员执行记录,结算单表保存金额明细。工单状态机单独设计,保证“用户取消订单”不影响“已完成工单”的结算。
// 工单状态机:明确禁止非法跳转 const ALLOWED_TRANSITIONS = { NEW: ['ASSIGNED', 'CANCELLED'], ASSIGNED: ['ACCEPTED', 'ASSIGNED', 'CANCELLED'], ACCEPTED: ['ARRIVED', 'CANCELLED'], ARRIVED: ['SERVING', 'CANCELLED'], SERVING: ['FINISHED', 'CANCELLED'], FINISHED: ['SETTLED', 'DISPUTED'] }; function canTransition(current, next) { return ALLOWED_TRANSITIONS[current]?.includes(next) ?? false; }参数说明:数组的 key 是工单当前状态,value 是该状态下允许跳转到的目标状态。ACCEPTED 允许再跳回 ASSIGNED,是因为服务人员接单后可能临时请假,需要人工重新分配。CANCELLED 是所有非终态在撤销条件下的合规出口,但 FINISHED 之后不能直接取消,只能走 DISPUTED 进入争议流程,这样结算引擎才不会被短账。
3.3 排班与调度:JavaScript函数级的最小匹配策略
家政调度不需要一开始就上复杂的运筹优化。起步阶段用 JavaScript 写一个确定性的匹配函数就够了:先按城市过滤,再匹配技能标签,然后从当天可用的服务人员里取最近被派单最少的人。
function matchProvider({ cityCode, skillTag, targetDate }) { return providerPool .filter(p => p.cityCode === cityCode && p.skillTags.includes(skillTag)) .filter(p => p.schedule[targetDate]?.slotFree) .sort((a, b) => a.lastOrderAt - b.lastOrderAt)[0] || null; }参数说明:第一个 filter 负责技能与城市匹配,第二个 filter 检查日期槽位是否空闲,两个 filter 串行执行,语义清晰。排序采用最近接单时间升序,让闲的人优先被派单,这是一种最简单的工作负载均衡。该函数在服务人员数据量超过一万时会有性能压力,届时再把过滤逻辑下沉到 SQL 或 Redis 索引,JavaScript 层只保留最后的排序策略。
4. 实时推送、工时回传与断线补偿:家政SaaS设计源码的JavaScript稳健层
家政平台对实时性的需求集中在两个点:一是管理员给服务人员派单时要立即收到通知,二是服务人员开始和结束服务时要把工时精确回传。网络不稳定时,工时回传不能丢、不能重复统计,这一章讲透如何用 JavaScript 实现稳健的补偿机制。
4.1 实时通道选型:WebSocket与页面工作台的监听边界
管理后台与服务人员工作台之间的消息通道通常选 WebSocket。服务人员的 H5 页面放进 App 的 WebView 后,每次切后台都可能被系统挂起,此时 WebSocket 连接会断开,但页面内用 JavaScript 监听的业务状态不会自动恢复。常见做法是:页面回到前台时主动检查连接状态,断线则重新握手,并拉取一次离线消息。
消息体要带类型和业务 ID,例如WORK_ORDER_ASSIGNED、WORK_ORDER_UPDATED。前端拿到消息后先做本地状态更新,再触发一次接口请求获取完整数据,避免消息体里的字段过期。监听器只做提示和跳转,不承担数据持久化职责。
4.2 工时回传的加密与幂等:seq与idempotent表
服务人员点击“开始服务”和“结束服务”时发送工时记录,这两个动作间隔可能长达数小时,中间发生断网的概率不低。JS 侧要生成单调递增的序列号 seq,并保存最近一条未确认消息。
let seq = 0; const pending = new Map(); async function sendWorkLog(workLog) { const message = { seq: ++seq, payload: workLog, retry: 0 }; pending.set(message.seq, message); try { const ack = await channel.send(message); pending.delete(message.seq); return ack; } catch (err) { if (message.retry < 4) { message.retry += 1; setTimeout(() => sendWorkLog(message.payload), 500 * 2 ** message.retry); } else { // 本地IndexedDB持久化,App下次启动时统一补传 saveToLocalDB(message); } } }参数说明:重试次数上限 4 次,退避间隔按500 * 2**retry递增,即 1秒、2秒、4秒、8秒。超过重试次数后写入 IndexedDB,下次启动再补传。服务端要用 providerNo 加 seq 作为联合唯一键,第一次处理成功后落幂等表,重复请求直接返回已确认结果,这样即使客户端重复发送也不会产生双倍工时记录。
4.3 跨午夜工单与休息段过滤:JavaScript的filter写在哪里
夜间保洁工单经常跨越零点,如果只按开始和结束的日期分别计算,会出现半天工时被切到错误账单日。设计源码时要把工单时间段作为一个整体,按分钟计算总时长,再扣除休息时段。
function workMinutesWithin(startISO, endISO, restPeriods = []) { const totalMs = new Date(endISO) - new Date(startISO); const restMs = restPeriods .filter(p => new Date(p.end) > new Date(startISO) && new Date(p.start) < new Date(endISO)) .reduce((sum, p) => sum + overlapMs(p, startISO, endISO), 0); return Math.round((totalMs - restMs) / 60000); }参数说明:restPeriods 是服务人员手动提交的休息区间数组,例如[{ start: '2025-06-01T12:00:00+08:00', end: '2025-06-01T13:00:00+08:00' }]。filter 先把与工单时间段无交集的休息段剔除,reduce 再把剩余部分的交集分钟数累加。这一个函数同时处理了跨午夜、多休息段和时长封顶三种逻辑,比按天拆分后再拼接要可靠得多。
5. 结算引擎与差错单:家政SaaS设计源码里最容易返工的模块
家政平台结算的核心不是简单地把客户付款转给服务人员,而是要把一笔订单金额拆分到平台佣金、服务人员提成、物料费和补贴等多个账户。拆错一分钱都会在对账时暴露,所以结算引擎必须从一开始就用整数分计算。
5.1 用整数分拆账:JavaScript的费用拆分函数与余额守恒
拆分规则用比例或固定金额表示。固定金额直接生效,比例金额先按比例分给前 N-1 个账户,最后一个账户接收剩余金额,保证拆分总额与订单总额严格相等。
function splitSettlement(totalCent, rules) { const parts = rules.map(r => ({ account: r.account, amount: 0, ratio: r.ratio })); let assigned = 0; parts.forEach((part, index) => { if (index === parts.length - 1) { part.amount = totalCent - assigned; // 最后一个账户吸收余数 } else { part.amount = Math.floor(totalCent * part.ratio); assigned += part.amount; } }); return parts; }参数说明:totalCent 是订单实际收款金额(单位分),rules 是账户拆分规则数组。比如平台佣金比例 0.1、服务人员比例 0.9,调用后会得到两个账户的金额。余数兜底放在最后一个账户,避免浮点误差。所有金额在展示层再转成元,计算层一律不允许出现小数。
5.2 结算批次与三方对账记录
每月结算要分两期,每期生成一个结算批次。批次表里记录服务人员、工单、原始订单收入、平台佣金、应付服务费和状态。状态机为:PAYING、SUCCESS、DISPUTED。打款结束后需要与第三方支付渠道回执核对,核对结果写入独立的对账记录表。
| 字段 | 含义 |
|---|---|
| settlement_id | 结算批次号,按月生成 |
| provider_id | 服务人员ID |
| work_order_id | 工单号 |
| gross_cent | 订单收入,单位分 |
| commission_cent | 平台佣金 |
| payable_cent | 应付服务费 |
| status | PAYING / SUCCESS / DISPUTED |
对账记录表至少要存三方数据:我方待付金额、第三方实际支付金额、差异金额。差异为 0 才能把批次状态置为 SUCCESS,差异不为 0 则自动生成差错单进入人工处理流程。差错单处理时不得直接修改原结算单,只能在调整表里追加记录,保证所有金额变化都有迹可循。
5.3 差错单处理:红差、蓝差与人工回滚的隔离
差异金额平台内部习惯叫红差和蓝差。蓝差指平台少付了,需要补款;红差指平台多付了,需要追回或在下期抵扣。差错单必须有人工审批状态,审批通过后由独立的对账脚本生成调整记录,而不是结算脚本直接改原单。
调整记录包含原结算单号、调整类型、金额和审批人。对账脚本每日跑一次,将待处理差错单与渠道流水重新比对,连续七天比对不通过的自动升级到人工处理队列。这样做的好处是结算引擎保持简单,所有异常都通过独立模块消化,线上问题能快速定位。
6. 发布前的CSP与监控白名单:家政SaaS设计源码的最后一道检查
部署前安全扫描经常会报“检测到目标站点存在javascript框架库漏洞”,这个漏洞通常是前端引入了带已知漏洞的第三方 JS 库,或者 CSP 策略没收紧。家政平台承载用户真实手机号和服务地址,发布前必须把这道检查做完。
6.1 用CSP收敛框架库漏洞暴露面
在 Node.js 服务端为所有页面响应设置 Content-Security-Policy。家政 H5 工作台经常需要内联样式,所以 style-src 允许 'unsafe-inline',但 script-src 要严格限制为自身域名和白名单资源域名。
app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', "default-src 'self'; script-src 'self' https://res.your-domain.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:" ); next(); });参数说明:script-src 收紧后,页面里所有外链脚本和 eval 都会被拦截,能很大程度降低被插入恶意脚本的风险。如果使用 HBuilderX 打包发行,注意确认资源域名与页面域名一致,否则要额外加进白名单。排查框架库漏洞时,重点看 package.json 里锁定的 JS 库版本是否在已知漏洞列表内,锁版本文件要提交到代码库,禁止用浮动版本发布。
6.2 生产环境JS错误上报的Sensitive Data过滤
生产环境的错误日志里经常夹带用户手机号、姓名等参数。错误上报队列要统一走前端采集函数,在上报前先做脱敏处理,避免监控平台变成数据泄露出口。
function sanitizeStack(stackText) { return stackText.replace( /(mobile|realName|certNo)=([^&\s]+)/g, '$1=***' ); }参数说明:正则匹配 URL 参数格式的敏感字段,把值替换成星号。上报队列只保留最近 20 条去重后的错误信息,超出上限按时间淘汰。运行时错误发生时,前端的 JavaScript 监听器捕获 window.onerror 和 unhandledrejection,把经过 sanitizeStack 处理的消息发送到/api/monitor/collect接口,该接口只接受固定字段。
6.3 用接口日志验证全链路:一条检查清单
上线后先用接口日志验证整条链路。第一步看/api/health响应头里有没有 CSP;第二步触发一次人工结算,检查对账记录里差异是否为 0;第三步模拟服务人员端断网回传,确认幂等表里只落一条数据。这三步都通过,家政SaaS 的订单、工时、结算闭环就具备最基本的可靠性。
本文还有配套的精品资源,点击获取