
简介这是一款基于纯JavaScript实现的紫微斗数在线命盘解析工具面向对传统命理有兴趣的普通用户也适合前端开发者研究星曜推算与可视化逻辑。用户输入出生时间即可生成命盘查看性格、事业、财运等分析无需后端服务即可在浏览器中直接运行。压缩包共13个文件、56KB主要包含5个js脚本命盘核心算法、农历转换、界面交互、1个html入口、1个css样式、3个png图标素材、2个bak备份文件及1个md说明文档结构精简清晰。已有231人学习。通过研读源码可掌握紫微斗数星曜分布规则、农历与公历转换方法、命盘图表的SVG绘制思路以及表单校验与实时更新的前端交互技巧。项目将算法、数据、UI分层组织结构清晰适合作为个性化命理应用或星座类小工具的开发起点同时也能加深对传统命理学基本概念的理解。1. 一套纯浏览器命盘引擎看到的不是玄学而是规则系统第一次拆这套ZiWeiDouShu-master源码时我本来预期里面塞满了不可维护的“魔法表”结果恰恰相反整个排盘过程可以被拆成农历转换、安星、定宫、四化四条清晰的规则链核心计算完全依赖lunar.jsziweicore.js不请求任何后端接口。这种设计特别适合那些需要离线可用、还要快速移植到小程序或桌面壳的命理类工具也适合想搞懂“农历、干支、五行局到底怎么换算”的前端工程师。如果你正在找工作里的算法题思路或者想看看老派前端如何用 jQuery 写出一套可交互的图表这份源码的阅读价值都在线。它不需要你相信命理只需要你理解“输入生辰 → 输出一张带星曜坐标的十二宫表”这一整套确定性逻辑。2. 历法先行lunar.js 与干支计时数据模型2.1 为什么排盘的第一步是公历转农历紫微斗数的输入是“公历年月日时”但几乎所有推算规则都建立在农历和干支之上。命宫要从农历正月、月支、时支推导紫微星定位需要农历生日数字十二宫的宫干又依赖年干。所以程序里必须有一份足够准确的农历转换器。项目里的lunar.js承担的就是这个职责它不依赖外部接口而是缓存了从某个起始年份到未来几十年的农历历表再配合“节气边界”来确定年柱和月柱。实操中常见做法是先把公历转成农历对象再从这个对象里取出年干、月支、日干支、时支等信息。下面是我重构时写的读取层兼容lunar.js的常用接口// 假设 lunar.js 暴露了 Solar 与 Lunar 的转换类 function getFourPillars(solarDate) { const lunar Lunar.fromDate(solarDate); // 得到农历对象 return { yearGanZhi: lunar.getYearInGanZhi(), // 年柱例如庚午 monthGanZhi: lunar.getMonthInGanZhi(), // 月柱例如辛巳 dayGanZhi: lunar.getDayInGanZhi(), // 日柱例如乙亥 timeGanZhi: lunar.getTimeInGanZhi(), // 时柱例如癸未 lunarMonth: lunar.getMonth(), lunarDay: lunar.getDay(), isLeap: lunar.getLeap() ! 0 }; }这里的关键点是getMonthInGanZhi()和简单的农历月份并不等价它必须以“节”为边界立春换年、惊蛰换月。如果直接用农历腊月数字排出的盘子往往差一个宫位。参数说明Lunar.fromDate()接收 JavaScriptDate对象getLeap()返回值非 0 时表示当前农历月是闰月紫微斗数对闰月的处理是“当月可重排或按下月”的分支点ziweicore.js内部通常用isLeap来决定是否走闰月分支。2.2 星曜与宫位的数据结构设计排盘不是一堆if/else核心是预先定义好“星曜表”和“宫位表”。项目中比较接近的数据结构是数组套对象每个宫位有索引、宫名、宫干、命宫标记每颗星有名称、庙旺落陷、是否主星等属性。下表是我从源码行为里还原出的常用字段字段类型说明palaceIndexnumber0~11对应十二宫palaceNamestring命宫、兄弟、夫妻等heavenlyStemstring由年干推出的宫干earthlyBranchstring子、丑、寅……亥majorStarsarray十四主星名称列表minorStarsarray辅星、煞星列表changesobject四化对应星曜这种设计的好处是渲染层只关心“某宫有哪些星”而排盘层只负责把星曜写入palaceIndex。ziweistar.js更像是“星曜知识库”它只输出星名与属性不参与宫位计算。真正计算宫位的是ziweicore.js两者职责分离这在新手写的同类型项目里很少见。2.3 五行局如何从生日反推紫微星定位依赖“五行局”而五行局由年干与农历生日共同决定。常见做法是维护一张二维映射表年干分五组甲乙为木、丙丁为火等生日则按“十进法”归入水二局、木三局、金四局、土五局、火六局。这部分网上有大量口诀表源码中直接硬编码为查表函数我也一样const FIVE_ELEMENT_MAP { 水二局: { unit: 2, conditions: [[初一..], ...] }, 木三局: { unit: 3, conditions: [...] }, ... }; function getElementBureau(yearGan, lunarDay) { // 根据年干找到可能的局再按生日区间命中唯一局 for (const bureauKey in FIVE_ELEMENT_MAP) { const item FIVE_ELEMENT_MAP[bureauKey]; if (item.conditions.some(cond cond.includes(lunarDay))) { return { name: bureauKey, unit: item.unit }; } } throw new Error(无法推算五行局); }这个函数返回的unit会直接用于“紫微星位置”的起算水二局从寅宫起“水二”每过两天移一个宫位直到越过生日。边界条件很多比如生日大于局数时要做除法取余余数为 0 时要留在原宫。实际项目里要特别当心数组索引从 0 还是 1 开始我排查过一个例子生日恰好是局数的整数倍时结果会差一格。3. 排盘算法主线命宫、身宫、十二宫与四化落点3.1 命宫身宫从月、时到宫的逆顺寻址拿到农历月和时支后命宫与身宫其实是一道典型的“坐标换算”。口诀是“寅宫起正月顺数生月宫再从生月宫起子时逆数至生时落点为命宫顺数至生时落点为身宫”。翻译成代码要维护一个“宫位序号表”子0、丑1、寅2……亥11然后用取模运算完成循环。我一般会先把宫位序列固定成数组再写一个双模式寻宫函数function locatePalace(month, hour, mode) { // 起始宫固定为寅索引2 const start 2; // 月支索引0子1丑2寅... const monthPalace (start month - 1) % 12; let target; if (mode ming) { target (monthPalace - hour 12) % 12; // 逆数 } else { target (monthPalace hour) % 12; // 顺数 } return target; // 0~11对应十二宫索引 }这里的month是农历月序号hour必须转换成时辰索引23~1 为子时索引01~3 为丑时索引1依此类推不能直接用当前小时。注意负数取模在 JavaScript 里会得到负数所以(monthPalace - hour 12) % 12的12是必需的。很多新手会在这翻车建议像我一样把取模封装成(a - b 12) % 12而不是依赖语言特性。得到命宫索引后十二宫的排列规则是固定的从命宫开始逆时针依次排兄弟、夫妻、子女、财帛、疾厄、迁移、仆役、官禄、田宅、福德、父母。这部分源码中可以直接看到一张数组表把“宫名”按逆序排列。3.2 紫微与天府的主星布列十四主星的位置不是一颗一颗手动写的而是把紫微星作为锚点根据“紫微同宫则天府在对宫隔六合五宫”的规则反算。传统排法里紫微星一旦定位天府星系和紫微星系的星曜会形成一一张固定映射表。以紫微所在宫为ziweiIndex天府所在宫是(ziweiIndex 6) % 12也就是紫微的对宫。然后按顺序布列天府、太阴、贪狼、巨门、天相、天梁、七杀、破军等。项目中ziweicore.js的逻辑可以浓缩成以下伪代码function placeMajorStars(ziweiIndex) { const result { tianfu: [], taiyin: [], tanlang: [], ... }; result.tianfu.index (ziweiIndex 6) % 12; // 天府星系按“府阴贪巨相梁杀破”位置间隔为1,1,1,1,1,3,1 const tianfuStars [天府,太阴,贪狼,巨门,天相,天梁,七杀,破军]; const gaps [1, 1, 1, 1, 1, 3, 1]; let current result.tianfu.index; tianfuStars.forEach((star, i) { result[star].index current; if (i gaps.length) current (current gaps[i]) % 12; }); // 紫微星系按“紫机阳武同”位置间隔依次为1,1,1,1 const ziweiStars [紫微,天机,太阳,武曲,天同]; const ziweiGaps [1, 1, 1, 1]; current ziweiIndex; ziweiStars.forEach((star, i) { result[star].index current; if (i ziweiGaps.length) current (current ziweiGaps[i]) % 12; }); return result; }注意紫微星系和天府星系是两套循环布星不能全都从紫微出发否则对宫不够对称。实际上当紫微在子、午等特殊位置时两星系会各占一半十二宫函数里的gaps就是按传统“紫微诀”压缩成的步长。这个表如果抄错命盘的后半部分位置会整体偏移一格。我的经验是先把经典命盘手工排一次再用代码输出比对。3.3 四化表十天干与化曜的二维映射四化算是排盘里最容易写错的部分。四化指“化禄、化权、化科、化忌”由出生年的天干决定哪些星曜发生变化。源码中通常写成{ 甲: [廉贞,破军,武曲,太阳], ... }这样的映射每行四颗星顺序固定是禄权科忌。我这边直接复制常用的四化表年干化禄化权化科化忌甲廉贞破军武曲太阳乙天机天梁紫微太阴丙天同天机文昌廉贞丁太阴天同天机巨门戊贪狼太阴右弼天机己武曲贪狼天梁文曲庚太阳武曲太阴天同辛巨门太阳文曲文昌壬天梁紫微左辅武曲癸破军巨门太阴贪狼实现四化时要注意表中出现的是“星曜名”但在代码里需要先找到该星落在哪个宫再把“化禄”标记追加到该星的changes字段而不是直接新增一颗星。ziweiui.js渲染时会把化忌用红色或特殊图标标出这就是为什么star.png和star-ico.png有几套不同颜色素材——UI 层不是根据星名换色而是根据changes是否有对应项来判断。写成函数的话常见做法是function applySiHua(yearGan, stars) { const table { 甲: [廉贞, 破军, 武曲, 太阳], 乙: [天机, 天梁, 紫微, 太阴], // 其余天干从映射表读取 }; const [lu, quan, ke, ji] table[yearGan] || []; stars.forEach(star { if (star.name lu) star.changes.lu true; if (star.name quan) star.changes.quan true; if (star.name ke) star.changes.ke true; if (star.name ji) star.changes.ji true; }); return stars; }这里一个常见的误用是把table直接做成二维数组而不校验yearGan导致遇到“甲己”合化等特殊输入时找不到键。另外“四化”里文昌、文曲、左辅、右弼也可能出现所以stars数组必须包含辅星不能只遍历十四主星。4. 从计算到呈现ziweiui.js 与 DOM 绘制流程4.1 表单输入与校验链这个项目是典型的多文件前端应用index.html里放表单jquery.min.js负责选择器和事件绑定ziweiui.js负责把计算得到的星曜数据渲染到十二宫棋盘。用户输入出生年、月、日、时后先走一遍前端校验再把数据交给ziweicore.js。典型的点击事件写法如下$(#btn-cal).on(click, function () { const year parseInt($(#birth-year).val(), 10); const month parseInt($(#birth-month).val(), 10); const day parseInt($(#birth-day).val(), 10); const hour parseInt($(#birth-hour).val(), 10); if (!year || !month || !day || hour undefined) { $(#error-msg).text(请完整填写出生年月日时); return; } const chart new ZiWeiCore().build(year, month, day, hour); renderPalace(chart); });校验逻辑不复杂但注意这里的hour不是小时区间而是“时辰编号”。我见过很多使用者在onchange事件里直接传hour: 14结果按 14 时辰去查表最后命宫怎么都不对。正确的做法是在index.html下拉框里就预置“子/丑/寅……”十六时辰让用户选时辰而不是小时。参数说明build(year, month, day, hour)接受的公历月是 1~12日范围会交给lunar.js判断hour 必须是 0~11 的时辰索引。4.2 命盘网格的渲染策略命盘的视觉结构是“十二宫网格 中宫”源码里用 CSS 定位一个#palace-grid区域然后把每宫渲染成绝对定位的div宫内的星曜再以img或span填进去。star_empty.png通常用来占位star.png和star-ico.png分别用于主星与辅星的图标。渲染函数可以抽象成两步第一步生成“宫位 - 星曜列表”的映射第二步遍历该映射向 DOM 插入节点。我一般会避免在循环中反复触发innerHTML而是拼好字符串后一次性写入function renderPalace(chart) { const grid document.getElementById(palace-grid); const html chart.palaces.map((palace, idx) { const stars palace.majorStars.concat(palace.minorStars) .map(star img src${star.img} alt${star.name} title${star.name}) .join(); return div classpalace>// verify.js const ZiWeiCore require(./ziweicore.js); const cases [ { input: [1990, 5, 15, 6], expect: { mingPalace: 辰, ziwei: 午 } }, { input: [1988, 10, 1, 2], expect: { mingPalace: 申, ziwei: 子 } } ]; cases.forEach(({ input, expect }) { const chart new ZiWeiCore().build(...input); const actual { mingPalace: chart.mingPalace, ziwei: chart.ziwei }; const assert JSON.stringify(actual) JSON.stringify(expect); console.log(input.join(-), assert ? PASS : FAIL ${JSON.stringify(actual)}); });第二步把ziweiui.js里所有 DOM 操作隔离出去保证核心模块不依赖document。这样 Node 环境能直接测试浏览器端只负责传入事件与渲染。第三步在 git 的 master 分支上配置 pre-commit 钩子每次提交前自动跑一遍上述测试。如果在你的团队里遇到! [remote rejected] master - master (pre-receive hook declined)那多半是钩子检测到了测试失败而不是权限问题先看 hook 日志再决定是修测试还是 update 断言。最后一个具体技巧调试市面上输出的“命盘图”时把鼠标悬浮到宫位里星曜图标上读取title中的星名与四化标记与ziweicore.js控制台输出的chart.palaces[2].changes做对比。我通常会在浏览器控制台里输入chart.palaces然后展开changes字段看化禄、化忌是否逐宫匹配。只要这一步稳定复现整个引擎的可靠性就托底了。本文还有配套的精品资源点击获取