紫微斗数排盘引擎源码解析:从农历转换到四化映射的规则链
2026/9/14 15:31:27 网站建设 项目流程

简介:这是一款基于纯JavaScript实现的紫微斗数在线命盘解析工具,面向对传统命理有兴趣的普通用户,也适合前端开发者研究星曜推算与可视化逻辑。用户输入出生时间即可生成命盘,查看性格、事业、财运等分析,无需后端服务即可在浏览器中直接运行。压缩包共13个文件、56KB,主要包含5个js脚本(命盘核心算法、农历转换、界面交互)、1个html入口、1个css样式、3个png图标素材、2个bak备份文件及1个md说明文档,结构精简清晰。已有231人学习。通过研读源码,可掌握紫微斗数星曜分布规则、农历与公历转换方法、命盘图表的SVG绘制思路,以及表单校验与实时更新的前端交互技巧。项目将算法、数据、UI分层组织,结构清晰,适合作为个性化命理应用或星座类小工具的开发起点,同时也能加深对传统命理学基本概念的理解。

1. 一套纯浏览器命盘引擎,看到的不是玄学而是规则系统

第一次拆这套ZiWeiDouShu-master源码时,我本来预期里面塞满了不可维护的“魔法表”,结果恰恰相反:整个排盘过程可以被拆成农历转换、安星、定宫、四化四条清晰的规则链,核心计算完全依赖lunar.js+ziweicore.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四化对应星曜

这种设计的好处是渲染层只关心“某宫有哪些星”,而排盘层只负责把星曜写入palaceIndexziweistar.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 为子时索引0,1~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.pngstar-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.pngstar-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 class="palace">// 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字段,看化禄、化忌是否逐宫匹配。只要这一步稳定复现,整个引擎的可靠性就托底了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询