十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于JavaScript的家政SaaS设计源码:状态机与结算引擎实践

基于JavaScript的家政SaaS设计源码:状态机与结算引擎实践 简介一套基于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 CHARSETutf8mb4;参数说明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:0008:00, end: 2025-06-01T13:00:0008: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服务人员IDwork_order_id工单号gross_cent订单收入单位分commission_cent平台佣金payable_cent应付服务费statusPAYING / 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 的订单、工时、结算闭环就具备最基本的可靠性。本文还有配套的精品资源点击获取
返回列表