dsh-metaphysics 0.14.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +213 -166
- package/README.md +229 -221
- package/client.js +1551 -111
- package/engine/bazi.js +505 -515
- package/engine/calendar.js +325 -256
- package/engine/crosscheck.js +133 -0
- package/engine/daliuren.js +339 -342
- package/engine/diff.js +197 -0
- package/engine/fail.js +13 -0
- package/engine/followup.js +173 -0
- package/engine/geo.js +1 -1
- package/engine/hepan.js +147 -144
- package/engine/index.js +566 -513
- package/engine/interpret-knowledge.js +277 -0
- package/engine/interpret-plain.js +197 -0
- package/engine/interpret.js +1356 -1176
- package/engine/liuyao.js +237 -238
- package/engine/lru.js +69 -0
- package/engine/meihua.js +137 -136
- package/engine/nayin.js +24 -0
- package/engine/qimen.js +442 -438
- package/engine/sanhe.js +26 -0
- package/engine/validate.js +56 -0
- package/engine/ziwei-hepan.js +123 -120
- package/engine/ziwei.js +821 -828
- package/index.js +229 -212
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -7,6 +7,167 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
if (!React) return { inject: [], apply() {} };
|
|
8
8
|
const h = React.createElement;
|
|
9
9
|
const __mods = {};
|
|
10
|
+
__mods.fail = (function () {
|
|
11
|
+
|
|
12
|
+
const fail = (code, message) => {
|
|
13
|
+
const e = new Error(message);
|
|
14
|
+
e.code = code;
|
|
15
|
+
return e;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return { fail };
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
__mods.validate = (function () {
|
|
22
|
+
const { fail } = __mods.fail;
|
|
23
|
+
|
|
24
|
+
const MIN_YEAR = 1600;
|
|
25
|
+
const MAX_YEAR = 2400;
|
|
26
|
+
|
|
27
|
+
function isLeapYear(year) {
|
|
28
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function daysInMonth(year, month) {
|
|
32
|
+
if (month === 2) return isLeapYear(year) ? 29 : 28;
|
|
33
|
+
return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] || 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const show = (v) => (typeof v === 'string' ? `「${v}」` : String(v));
|
|
37
|
+
const HINT = '引擎只接受解析好的公历字段 { year, month, day, hour?, minute? };字符串请先交给宿主层的 parseDateTime 解析';
|
|
38
|
+
|
|
39
|
+
function assertCivil(input, who = '') {
|
|
40
|
+
const p = who ? `${who} 的` : '';
|
|
41
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
42
|
+
const got = input === null ? 'null' : Array.isArray(input) ? '数组' : typeof input;
|
|
43
|
+
throw fail('E_INPUT', `${p}入参必须是对象(收到 ${got});${HINT}`);
|
|
44
|
+
}
|
|
45
|
+
const hour = input.hour === undefined ? 0 : input.hour;
|
|
46
|
+
const minute = input.minute === undefined ? 0 : input.minute;
|
|
47
|
+
for (const [key, value] of [['year', input.year], ['month', input.month], ['day', input.day], ['hour', hour], ['minute', minute]]) {
|
|
48
|
+
if (!Number.isInteger(value)) throw fail('E_INPUT', `${p}${key} 必须是整数(收到 ${show(value)});${HINT}`);
|
|
49
|
+
}
|
|
50
|
+
if (input.year < MIN_YEAR || input.year > MAX_YEAR) {
|
|
51
|
+
throw fail('E_INPUT', `${p}year 超出支持范围 ${MIN_YEAR}–${MAX_YEAR}(收到 ${input.year});更早的年份时区与历法口径不可靠`);
|
|
52
|
+
}
|
|
53
|
+
if (input.month < 1 || input.month > 12) {
|
|
54
|
+
throw fail('E_INPUT', `${p}month 超出范围 1–12(收到 ${input.month})`);
|
|
55
|
+
}
|
|
56
|
+
const max = daysInMonth(input.year, input.month);
|
|
57
|
+
if (input.day < 1 || input.day > max) {
|
|
58
|
+
throw fail('E_INPUT', `${p}日期不存在:${input.year}-${input.month}-${input.day}(${input.year} 年 ${input.month} 月只有 ${max} 天)`);
|
|
59
|
+
}
|
|
60
|
+
if (hour < 0 || hour > 23) throw fail('E_INPUT', `${p}hour 超出范围 0–23(收到 ${hour})`);
|
|
61
|
+
if (minute < 0 || minute > 59) throw fail('E_INPUT', `${p}minute 超出范围 0–59(收到 ${minute})`);
|
|
62
|
+
return input;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { MIN_YEAR, MAX_YEAR, isLeapYear, daysInMonth, assertCivil };
|
|
66
|
+
})();
|
|
67
|
+
|
|
68
|
+
__mods.lru = (function () {
|
|
69
|
+
|
|
70
|
+
function createLru({ max = 64, ttlMs = 0, name = 'lru', now = () => Date.now() } = {}) {
|
|
71
|
+
const map = new Map();
|
|
72
|
+
const stats = { name, hits: 0, misses: 0, evictions: 0, expirations: 0 };
|
|
73
|
+
|
|
74
|
+
const expired = (entry) => ttlMs > 0 && now() - entry.at > ttlMs;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
name,
|
|
78
|
+
max,
|
|
79
|
+
ttlMs,
|
|
80
|
+
get(key) {
|
|
81
|
+
const entry = map.get(key);
|
|
82
|
+
if (!entry || expired(entry)) {
|
|
83
|
+
if (entry) { map.delete(key); stats.expirations += 1; }
|
|
84
|
+
stats.misses += 1;
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
map.delete(key);
|
|
88
|
+
map.set(key, entry);
|
|
89
|
+
stats.hits += 1;
|
|
90
|
+
return entry.value;
|
|
91
|
+
},
|
|
92
|
+
set(key, value) {
|
|
93
|
+
if (map.has(key)) map.delete(key);
|
|
94
|
+
map.set(key, { value, at: now() });
|
|
95
|
+
while (map.size > max) {
|
|
96
|
+
map.delete(map.keys().next().value);
|
|
97
|
+
stats.evictions += 1;
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
},
|
|
101
|
+
has(key) {
|
|
102
|
+
const entry = map.get(key);
|
|
103
|
+
if (!entry) return false;
|
|
104
|
+
if (expired(entry)) { map.delete(key); stats.expirations += 1; return false; }
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
delete(key) { return map.delete(key); },
|
|
108
|
+
clear() { map.clear(); },
|
|
109
|
+
get size() { return map.size; },
|
|
110
|
+
stats() {
|
|
111
|
+
const total = stats.hits + stats.misses;
|
|
112
|
+
return {
|
|
113
|
+
name,
|
|
114
|
+
size: map.size,
|
|
115
|
+
max,
|
|
116
|
+
hits: stats.hits,
|
|
117
|
+
misses: stats.misses,
|
|
118
|
+
evictions: stats.evictions,
|
|
119
|
+
expirations: stats.expirations,
|
|
120
|
+
hitRate: total ? stats.hits / total : 0,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { createLru };
|
|
127
|
+
})();
|
|
128
|
+
|
|
129
|
+
__mods.nayin = (function () {
|
|
130
|
+
|
|
131
|
+
const NAYIN_NAMES = [
|
|
132
|
+
'海中金', '炉中火', '大林木', '路旁土', '剑锋金',
|
|
133
|
+
'山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
|
|
134
|
+
'泉中水', '屋上土', '霹雳火', '松柏木', '长流水',
|
|
135
|
+
'沙中金', '山下火', '平地木', '壁上土', '金箔金',
|
|
136
|
+
'覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木',
|
|
137
|
+
'大溪水', '沙中土', '天上火', '石榴木', '大海水',
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
function nayinOf(ganzhiIdx) {
|
|
141
|
+
const i = ((Number(ganzhiIdx) % 60) + 60) % 60;
|
|
142
|
+
const name = NAYIN_NAMES[Math.floor(i / 2)];
|
|
143
|
+
return { name, element: name[2] };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { NAYIN_NAMES, nayinOf };
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
__mods.sanhe = (function () {
|
|
150
|
+
|
|
151
|
+
const SANHE_GROUPS = [
|
|
152
|
+
{ branches: ['申', '子', '辰'], element: '水' },
|
|
153
|
+
{ branches: ['亥', '卯', '未'], element: '木' },
|
|
154
|
+
{ branches: ['寅', '午', '戌'], element: '火' },
|
|
155
|
+
{ branches: ['巳', '酉', '丑'], element: '金' },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const SANHE = Object.fromEntries(SANHE_GROUPS.map((g) => [g.branches.join(''), g.element]));
|
|
159
|
+
|
|
160
|
+
const SANHE_ELEMENT = Object.fromEntries(
|
|
161
|
+
SANHE_GROUPS.flatMap((g) => g.branches.map((b) => [b, g.element])),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
function sanheGroupOf(branch) {
|
|
165
|
+
return SANHE_GROUPS.find((g) => g.branches.includes(branch)) || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { SANHE_GROUPS, SANHE, SANHE_ELEMENT, sanheGroupOf };
|
|
169
|
+
})();
|
|
170
|
+
|
|
10
171
|
__mods['vsop-earth'] = (function () {
|
|
11
172
|
const L0 = [
|
|
12
173
|
[1.75347045673,0,0],[0.03341656453,4.66925680415,6283.0758499914],[0.00034894275,4.62610242189,12566.1516999828],[0.00003417572,2.82886579754,3.523118349],
|
|
@@ -49,6 +210,7 @@ return { earthL };
|
|
|
49
210
|
|
|
50
211
|
__mods.calendar = (function () {
|
|
51
212
|
const { earthL } = __mods['vsop-earth'];
|
|
213
|
+
const { createLru } = __mods.lru;
|
|
52
214
|
|
|
53
215
|
const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
54
216
|
const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
@@ -165,19 +327,26 @@ function localOffsetFor(jdUt) {
|
|
|
165
327
|
return jdUt < CUTOVER_JD ? OFFSET_BEIJING_LOCAL : OFFSET_MODERN;
|
|
166
328
|
}
|
|
167
329
|
|
|
168
|
-
const TERM_INSTANT_CACHE =
|
|
169
|
-
|
|
330
|
+
const TERM_INSTANT_CACHE = createLru({ max: 4096, name: 'termInstant' });
|
|
331
|
+
|
|
332
|
+
const TERM_CONVERGE_DEG = 1e-5;
|
|
333
|
+
|
|
334
|
+
const TERM_GUESS_CACHE = new Map();
|
|
335
|
+
const TROPICAL_YEAR = 365.2422;
|
|
170
336
|
|
|
171
337
|
function computeTermInstantJd(year, index) {
|
|
172
338
|
const target = termLongitude(index);
|
|
173
339
|
const anchor = toJDN(year, 1, 5) - 0.5 + index * 15.2184;
|
|
174
|
-
|
|
340
|
+
const prev = TERM_GUESS_CACHE.get(index);
|
|
341
|
+
let jd = prev !== undefined ? prev + TROPICAL_YEAR : anchor;
|
|
342
|
+
if (Math.abs(jd - anchor) > 20) jd = anchor;
|
|
175
343
|
for (let n = 0; n < 12; n++) {
|
|
176
344
|
let diff = target - sunLongitude(jd);
|
|
177
345
|
diff = ((((diff + 180) % 360) + 360) % 360) - 180;
|
|
178
346
|
jd += diff / 0.98564736;
|
|
179
|
-
if (Math.abs(diff) <
|
|
347
|
+
if (Math.abs(diff) < TERM_CONVERGE_DEG) break;
|
|
180
348
|
}
|
|
349
|
+
TERM_GUESS_CACHE.set(index, jd);
|
|
181
350
|
const jdUt = jd - deltaTSeconds(year) / 86400;
|
|
182
351
|
return jdUt + localOffsetFor(jdUt);
|
|
183
352
|
}
|
|
@@ -187,9 +356,6 @@ function termInstantJd(year, index) {
|
|
|
187
356
|
const hit = TERM_INSTANT_CACHE.get(key);
|
|
188
357
|
if (hit !== undefined) return hit;
|
|
189
358
|
const jd = computeTermInstantJd(year, index);
|
|
190
|
-
if (TERM_INSTANT_CACHE.size >= TERM_INSTANT_CACHE_MAX) {
|
|
191
|
-
TERM_INSTANT_CACHE.delete(TERM_INSTANT_CACHE.keys().next().value);
|
|
192
|
-
}
|
|
193
359
|
TERM_INSTANT_CACHE.set(key, jd);
|
|
194
360
|
return jd;
|
|
195
361
|
}
|
|
@@ -218,26 +384,58 @@ function equationOfTime(year, month, day) {
|
|
|
218
384
|
return 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
|
|
219
385
|
}
|
|
220
386
|
|
|
221
|
-
const TERM_WINDOW_CACHE =
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
387
|
+
const TERM_WINDOW_CACHE = createLru({ max: 8, name: 'termWindow' });
|
|
388
|
+
const TERM_YEAR_CACHE = createLru({ max: 32, name: 'termYear' });
|
|
389
|
+
const ALL_TERM_INDEXES = Array.from({ length: 24 }, (_, i) => i);
|
|
390
|
+
const JIE_TERM_INDEXES = ALL_TERM_INDEXES.filter((i) => i % 2 === 0);
|
|
391
|
+
|
|
392
|
+
function termYearList(year, jieOnly) {
|
|
393
|
+
const key = jieOnly ? `${year}|jie` : `${year}|all`;
|
|
394
|
+
const hit = TERM_YEAR_CACHE.get(key);
|
|
395
|
+
if (hit) return hit;
|
|
396
|
+
if (jieOnly) {
|
|
397
|
+
const full = TERM_YEAR_CACHE.get(`${year}|all`);
|
|
398
|
+
if (full) {
|
|
399
|
+
const jie = full.filter((t) => t.index % 2 === 0);
|
|
400
|
+
TERM_YEAR_CACHE.set(key, jie);
|
|
401
|
+
return jie;
|
|
231
402
|
}
|
|
232
403
|
}
|
|
233
|
-
list
|
|
234
|
-
|
|
235
|
-
|
|
404
|
+
const list = [];
|
|
405
|
+
for (const i of (jieOnly ? JIE_TERM_INDEXES : ALL_TERM_INDEXES)) {
|
|
406
|
+
list.push({ year, index: i, name: TERM_NAMES[i], jd: termInstantJd(year, i) });
|
|
236
407
|
}
|
|
237
|
-
|
|
408
|
+
list.sort((a, b) => a.jd - b.jd);
|
|
409
|
+
TERM_YEAR_CACHE.set(key, list);
|
|
410
|
+
return list;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function termWindow(year, opts = {}) {
|
|
414
|
+
const jieOnly = opts.jieOnly === true;
|
|
415
|
+
const cacheKey = jieOnly ? `${year}|jie` : year;
|
|
416
|
+
const cached = TERM_WINDOW_CACHE.get(cacheKey);
|
|
417
|
+
if (cached) return cached;
|
|
418
|
+
const list = [
|
|
419
|
+
...termYearList(year - 1, jieOnly),
|
|
420
|
+
...termYearList(year, jieOnly),
|
|
421
|
+
...termYearList(year + 1, jieOnly),
|
|
422
|
+
];
|
|
423
|
+
list.sort((a, b) => a.jd - b.jd);
|
|
424
|
+
TERM_WINDOW_CACHE.set(cacheKey, list);
|
|
238
425
|
return list;
|
|
239
426
|
}
|
|
240
427
|
|
|
428
|
+
function xunKongOf(ganzhi) {
|
|
429
|
+
if (typeof ganzhi !== 'string' || ganzhi.length < 2) return [];
|
|
430
|
+
const s = STEMS.indexOf(ganzhi[0]);
|
|
431
|
+
const b = BRANCHES.indexOf(ganzhi[1]);
|
|
432
|
+
if (s < 0 || b < 0) return [];
|
|
433
|
+
const idx = sexagenaryIndex(s, b);
|
|
434
|
+
if (idx < 0) return [];
|
|
435
|
+
const head = Math.floor(idx / 10) * 10;
|
|
436
|
+
return [BRANCHES[(head + 10) % 12], BRANCHES[(head + 11) % 12]];
|
|
437
|
+
}
|
|
438
|
+
|
|
241
439
|
function previousJie(terms, jd) {
|
|
242
440
|
let found = null;
|
|
243
441
|
for (const t of terms) {
|
|
@@ -262,7 +460,11 @@ function formatJd(jd) {
|
|
|
262
460
|
return `${p.year}-${pad(p.month)}-${pad(p.day)} ${pad(p.hour)}:${pad(p.minute)}`;
|
|
263
461
|
}
|
|
264
462
|
|
|
265
|
-
|
|
463
|
+
function cacheMetrics() {
|
|
464
|
+
return [TERM_INSTANT_CACHE.stats(), TERM_YEAR_CACHE.stats(), TERM_WINDOW_CACHE.stats()];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { STEMS, BRANCHES, TERM_NAMES, termLongitude, toJDN, fromJDN, sexagenaryIndex, ganzhiName, dayGanzhiIndex, sunLongitude, deltaTSeconds, OFFSET_MODERN, OFFSET_BEIJING_LOCAL, CUTOVER_JD, localOffsetFor, termInstantJd, jdToParts, partsToJd, equationOfTime, termWindow, xunKongOf, previousJie, nextJie, formatJd, cacheMetrics };
|
|
266
468
|
})();
|
|
267
469
|
|
|
268
470
|
__mods.lunar = (function () {
|
|
@@ -544,7 +746,7 @@ function resolveAddress(input) {
|
|
|
544
746
|
|
|
545
747
|
const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
546
748
|
|
|
547
|
-
const USER_AGENT = 'dsh-metaphysics/0.
|
|
749
|
+
const USER_AGENT = 'dsh-metaphysics/0.16.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
548
750
|
|
|
549
751
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
550
752
|
if (typeof fetch !== 'function') return null;
|
|
@@ -1326,9 +1528,12 @@ return { tenGod, elementRoles, scoreLuck, analyzeBaziFrame };
|
|
|
1326
1528
|
})();
|
|
1327
1529
|
|
|
1328
1530
|
__mods.bazi = (function () {
|
|
1329
|
-
const { STEMS, BRANCHES, TERM_NAMES, ganzhiName, dayGanzhiIndex, jdToParts, partsToJd, sexagenaryIndex, termWindow, previousJie, nextJie, equationOfTime, formatJd } = __mods.calendar;
|
|
1531
|
+
const { STEMS, BRANCHES, TERM_NAMES, ganzhiName, dayGanzhiIndex, jdToParts, partsToJd, sexagenaryIndex, termWindow, previousJie, nextJie, equationOfTime, formatJd, xunKongOf } = __mods.calendar;
|
|
1330
1532
|
const { resolveAddress } = __mods.geo;
|
|
1331
1533
|
const { computeBaziShensha } = __mods.shensha;
|
|
1534
|
+
const { assertCivil } = __mods.validate;
|
|
1535
|
+
const { nayinOf } = __mods.nayin;
|
|
1536
|
+
const { SANHE } = __mods.sanhe;
|
|
1332
1537
|
const { analyzeBaziFrame, scoreLuck } = __mods.geju;
|
|
1333
1538
|
|
|
1334
1539
|
const HIDDEN_STEMS = {
|
|
@@ -1367,7 +1572,6 @@ const ROOT_HIDDEN_WEIGHT = [1.2, 0.6, 0.3];
|
|
|
1367
1572
|
const STEM_HE = { 甲己: '土', 乙庚: '金', 丙辛: '水', 丁壬: '木', 戊癸: '火' };
|
|
1368
1573
|
const STEM_CHONG_PAIRS = ['甲庚', '乙辛', '丙壬', '丁癸'];
|
|
1369
1574
|
const BRANCH_HE = { 子丑: '土', 寅亥: '木', 卯戌: '火', 辰酉: '金', 巳申: '水', 午未: '土' };
|
|
1370
|
-
const SANHE = { 申子辰: '水', 亥卯未: '木', 寅午戌: '火', 巳酉丑: '金' };
|
|
1371
1575
|
const SANHUI = { 寅卯辰: '木', 巳午未: '火', 申酉戌: '金', 亥子丑: '水' };
|
|
1372
1576
|
const BRANCH_CHONG = ['子午', '丑未', '寅申', '卯酉', '辰戌', '巳亥'];
|
|
1373
1577
|
const BRANCH_HAI = ['子未', '丑午', '寅巳', '卯辰', '申亥', '酉戌'];
|
|
@@ -1469,17 +1673,8 @@ function tenGod(dayStem, otherStem) {
|
|
|
1469
1673
|
return '?';
|
|
1470
1674
|
}
|
|
1471
1675
|
|
|
1472
|
-
const NAYIN_NAMES = [
|
|
1473
|
-
'海中金', '炉中火', '大林木', '路旁土', '剑锋金',
|
|
1474
|
-
'山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
|
|
1475
|
-
'泉中水', '屋上土', '霹雳火', '松柏木', '长流水',
|
|
1476
|
-
'沙中金', '山下火', '平地木', '壁上土', '金箔金',
|
|
1477
|
-
'覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木',
|
|
1478
|
-
'大溪水', '沙中土', '天上火', '石榴木', '大海水',
|
|
1479
|
-
];
|
|
1480
|
-
|
|
1481
1676
|
function nayin(ganzhiIdx) {
|
|
1482
|
-
return
|
|
1677
|
+
return nayinOf(ganzhiIdx).name;
|
|
1483
1678
|
}
|
|
1484
1679
|
|
|
1485
1680
|
const JIE_TO_MONTH_BRANCH = { 0: 1, 2: 2, 4: 3, 6: 4, 8: 5, 10: 6, 12: 7, 14: 8, 16: 9, 18: 10, 20: 11, 22: 0 };
|
|
@@ -1520,6 +1715,7 @@ function applyTrueSolar(jd, longitude) {
|
|
|
1520
1715
|
}
|
|
1521
1716
|
|
|
1522
1717
|
function buildBazi(input) {
|
|
1718
|
+
assertCivil(input);
|
|
1523
1719
|
const { year, month, day, hour = 0, minute = 0, gender = 'male' } = input;
|
|
1524
1720
|
const lateZi = input.lateZi !== false;
|
|
1525
1721
|
const useTrueSolar = input.trueSolar !== false;
|
|
@@ -1532,7 +1728,7 @@ function buildBazi(input) {
|
|
|
1532
1728
|
const solar = useTrueSolar ? applyTrueSolar(civilJd, longitude) : { offsetMinutes: 0, jd: civilJd, note: '未启用真太阳时' };
|
|
1533
1729
|
const jd = solar.jd;
|
|
1534
1730
|
|
|
1535
|
-
const terms = termWindow(year);
|
|
1731
|
+
const terms = termWindow(year, { jieOnly: true });
|
|
1536
1732
|
const prevJieTerm = previousJie(terms, jd);
|
|
1537
1733
|
const nextJieTerm = nextJie(terms, jd);
|
|
1538
1734
|
|
|
@@ -1626,12 +1822,6 @@ function buildBazi(input) {
|
|
|
1626
1822
|
return CHANGSHENG[((step % 12) + 12) % 12];
|
|
1627
1823
|
}
|
|
1628
1824
|
|
|
1629
|
-
function xunKong(ganzhi) {
|
|
1630
|
-
const idx = sexagenaryIndex(STEMS.indexOf(ganzhi[0]), BRANCHES.indexOf(ganzhi[1]));
|
|
1631
|
-
const start = Math.floor(idx / 10) * 10;
|
|
1632
|
-
return [BRANCHES[(start + 10) % 12], BRANCHES[(start + 11) % 12]];
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
1825
|
const monthBranch = pillars.month.branch;
|
|
1636
1826
|
|
|
1637
1827
|
const shensha = computeBaziShensha(pillars, kongwang);
|
|
@@ -1640,7 +1830,7 @@ function buildBazi(input) {
|
|
|
1640
1830
|
const pl = pillars[key];
|
|
1641
1831
|
pl.dishi = changsheng(dayStem, pl.branch);
|
|
1642
1832
|
pl.zizuo = changsheng(pl.stem, pl.branch);
|
|
1643
|
-
pl.kongwang =
|
|
1833
|
+
pl.kongwang = xunKongOf(pl.ganzhi);
|
|
1644
1834
|
pl.shensha = shensha.detail[key].map((x) => x.name);
|
|
1645
1835
|
pl.shenshaDetail = shensha.detail[key];
|
|
1646
1836
|
}
|
|
@@ -1798,7 +1988,7 @@ return { HIDDEN_STEMS, STEM_ELEMENT, BRANCH_ELEMENT, buildRelations, tenGod, nay
|
|
|
1798
1988
|
})();
|
|
1799
1989
|
|
|
1800
1990
|
__mods.liuyao = (function () {
|
|
1801
|
-
const { STEMS, BRANCHES, toJDN, ganzhiName, dayGanzhiIndex, sexagenaryIndex } = __mods.calendar;
|
|
1991
|
+
const { STEMS, BRANCHES, toJDN, ganzhiName, dayGanzhiIndex, sexagenaryIndex, xunKongOf } = __mods.calendar;
|
|
1802
1992
|
|
|
1803
1993
|
const TRIGRAM_BITS = {
|
|
1804
1994
|
乾: [1, 1, 1], 兑: [1, 1, 0], 离: [1, 0, 1], 震: [1, 0, 0],
|
|
@@ -1944,9 +2134,7 @@ function buildLiuyao(input = {}) {
|
|
|
1944
2134
|
|
|
1945
2135
|
const dayIdx = sexagenaryIndex(STEMS.indexOf(dayGanZhi.stem), BRANCHES.indexOf(dayGanZhi.branch));
|
|
1946
2136
|
const xun = dayIdx >= 0 ? Math.floor(dayIdx / 10) : -1;
|
|
1947
|
-
const xunKong =
|
|
1948
|
-
? [BRANCHES[(10 - 2 * xun + 12) % 12], BRANCHES[(11 - 2 * xun + 12) % 12]]
|
|
1949
|
-
: [];
|
|
2137
|
+
const xunKong = xunKongOf(`${dayGanZhi.stem}${dayGanZhi.branch}`);
|
|
1950
2138
|
const xunShou = xun >= 0 ? ganzhiName(xun * 10).name : '';
|
|
1951
2139
|
|
|
1952
2140
|
function decorate(hex, movingSet) {
|
|
@@ -2028,6 +2216,8 @@ __mods.ziwei = (function () {
|
|
|
2028
2216
|
const { STEMS, BRANCHES, ganzhiName, sexagenaryIndex, jdToParts, partsToJd, formatJd } = __mods.calendar;
|
|
2029
2217
|
const { solarToLunar } = __mods.lunar;
|
|
2030
2218
|
const { applyTrueSolar } = __mods.bazi;
|
|
2219
|
+
const { assertCivil } = __mods.validate;
|
|
2220
|
+
const { nayinOf } = __mods.nayin;
|
|
2031
2221
|
const { resolveAddress } = __mods.geo;
|
|
2032
2222
|
|
|
2033
2223
|
const fix = (i) => ((i % 12) + 12) % 12;
|
|
@@ -2045,18 +2235,8 @@ const BUREAU = {
|
|
|
2045
2235
|
火: { name: '火六局', number: 6 },
|
|
2046
2236
|
};
|
|
2047
2237
|
|
|
2048
|
-
const NAYIN_NAMES = [
|
|
2049
|
-
'海中金', '炉中火', '大林木', '路旁土', '剑锋金',
|
|
2050
|
-
'山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
|
|
2051
|
-
'泉中水', '屋上土', '霹雳火', '松柏木', '长流水',
|
|
2052
|
-
'沙中金', '山下火', '平地木', '壁上土', '金箔金',
|
|
2053
|
-
'覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木',
|
|
2054
|
-
'大溪水', '沙中土', '天上火', '石榴木', '大海水',
|
|
2055
|
-
];
|
|
2056
|
-
|
|
2057
2238
|
function nayinElement(ganzhiIdx) {
|
|
2058
|
-
|
|
2059
|
-
return { name, element: name[2] };
|
|
2239
|
+
return nayinOf(ganzhiIdx);
|
|
2060
2240
|
}
|
|
2061
2241
|
|
|
2062
2242
|
const ZIWEI_GROUP = [
|
|
@@ -2203,6 +2383,7 @@ function xiaoxianAge(branchIndex, triad, gender) {
|
|
|
2203
2383
|
}
|
|
2204
2384
|
|
|
2205
2385
|
function buildZiwei(input) {
|
|
2386
|
+
assertCivil(input);
|
|
2206
2387
|
const { year: cy, month: cm, day: cd, hour: ch = 0, minute: cmin = 0, gender = 'male' } = input;
|
|
2207
2388
|
const useTrueSolar = input.trueSolar !== false;
|
|
2208
2389
|
const geo = input.geo || (input.address ? resolveAddress(input.address) : null);
|
|
@@ -3160,6 +3341,7 @@ return { MAJOR, SUITS, MINOR, ROMAN, SPREADS, tarotDeck, tarotReading, drawTarot
|
|
|
3160
3341
|
__mods.meihua = (function () {
|
|
3161
3342
|
const { BRANCHES } = __mods.calendar;
|
|
3162
3343
|
const { solarToLunar } = __mods.lunar;
|
|
3344
|
+
const { fail } = __mods.fail;
|
|
3163
3345
|
const { hexagramFromBits } = __mods.liuyao;
|
|
3164
3346
|
|
|
3165
3347
|
const TRI_NAME = ['', '乾', '兑', '离', '震', '巽', '坎', '艮', '坤'];
|
|
@@ -3202,7 +3384,7 @@ function buildMeihua(input = {}) {
|
|
|
3202
3384
|
if (mode === 'number') {
|
|
3203
3385
|
const a = Number(input.upper);
|
|
3204
3386
|
const b = Number(input.lower);
|
|
3205
|
-
if (!Number.isFinite(a) || !Number.isFinite(b)) throw
|
|
3387
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) throw fail('E_INPUT', '数字起卦需要两个数字(upper / lower),例如 upper=3、lower=5');
|
|
3206
3388
|
upperNum = mod8(a);
|
|
3207
3389
|
lowerNum = mod8(b);
|
|
3208
3390
|
moving = Number.isFinite(Number(input.changing)) && Number(input.changing) > 0
|
|
@@ -3270,9 +3452,9 @@ function buildMeihua(input = {}) {
|
|
|
3270
3452
|
}
|
|
3271
3453
|
|
|
3272
3454
|
function parseDateTime(text) {
|
|
3273
|
-
if (typeof text !== 'string' || !text.trim()) throw
|
|
3455
|
+
if (typeof text !== 'string' || !text.trim()) throw fail('E_INPUT', '时间起卦需要 datetime,例如 2026-10-24T11:43');
|
|
3274
3456
|
const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2}))?$/);
|
|
3275
|
-
if (!m) throw
|
|
3457
|
+
if (!m) throw fail('E_INPUT', `无法解析 datetime:「${text}」,请用 2026-10-24T11:43 这种格式`);
|
|
3276
3458
|
return {
|
|
3277
3459
|
year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
|
|
3278
3460
|
hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
|
|
@@ -3283,7 +3465,9 @@ return { TRI_NAME, TRI_ELEMENT, TRI_BITS, buildMeihua };
|
|
|
3283
3465
|
})();
|
|
3284
3466
|
|
|
3285
3467
|
__mods.daliuren = (function () {
|
|
3286
|
-
const { toJDN, dayGanzhiIndex, ganzhiName, termWindow, formatJd } = __mods.calendar;
|
|
3468
|
+
const { toJDN, dayGanzhiIndex, ganzhiName, termWindow, formatJd, xunKongOf } = __mods.calendar;
|
|
3469
|
+
const { assertCivil } = __mods.validate;
|
|
3470
|
+
const { SANHE_ELEMENT } = __mods.sanhe;
|
|
3287
3471
|
|
|
3288
3472
|
const GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
3289
3473
|
const ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
@@ -3341,7 +3525,6 @@ const DAY_NIGHT_MAP = {
|
|
|
3341
3525
|
|
|
3342
3526
|
const RI_LU = { 甲: '寅', 乙: '卯', 丙: '巳', 丁: '午', 戊: '巳', 己: '午', 庚: '申', 辛: '酉', 壬: '亥', 癸: '子' };
|
|
3343
3527
|
const YANG_REN = { 甲: '卯', 乙: '辰', 丙: '午', 丁: '未', 戊: '午', 己: '未', 庚: '酉', 辛: '戌', 壬: '子', 癸: '丑' };
|
|
3344
|
-
const SAN_HE = { 申: '水', 子: '水', 辰: '水', 寅: '火', 午: '火', 戌: '火', 巳: '金', 酉: '金', 丑: '金', 亥: '木', 卯: '木', 未: '木' };
|
|
3345
3528
|
const SAN_HE_SHEN = {
|
|
3346
3529
|
水: { 驿马: '寅', 桃花: '酉', 华盖: '辰', 劫煞: '巳', 亡神: '亥' },
|
|
3347
3530
|
火: { 驿马: '申', 桃花: '卯', 华盖: '戌', 劫煞: '亥', 亡神: '巳' },
|
|
@@ -3358,7 +3541,7 @@ function calcShensha(dayGz) {
|
|
|
3358
3541
|
};
|
|
3359
3542
|
if (RI_LU[gan]) push('日禄', RI_LU[gan], '日干临官之位');
|
|
3360
3543
|
if (YANG_REN[gan]) push('羊刃', YANG_REN[gan], '日干帝旺之位');
|
|
3361
|
-
const trio = SAN_HE_SHEN[
|
|
3544
|
+
const trio = SAN_HE_SHEN[SANHE_ELEMENT[zhi]];
|
|
3362
3545
|
if (trio) {
|
|
3363
3546
|
push('驿马', trio.驿马, '日支三合冲位,主迁动');
|
|
3364
3547
|
push('桃花', trio.桃花, '咸池,主情缘');
|
|
@@ -3369,11 +3552,6 @@ function calcShensha(dayGz) {
|
|
|
3369
3552
|
return out;
|
|
3370
3553
|
}
|
|
3371
3554
|
|
|
3372
|
-
const XUN_KONG = {
|
|
3373
|
-
甲子: ['戌', '亥'], 甲戌: ['申', '酉'], 甲申: ['午', '未'],
|
|
3374
|
-
甲午: ['辰', '巳'], 甲辰: ['寅', '卯'], 甲寅: ['子', '丑'],
|
|
3375
|
-
};
|
|
3376
|
-
|
|
3377
3555
|
const JIA_ZI_60 = Array.from({ length: 60 }, (_, i) => GAN[i % 10] + ZHI[i % 12]);
|
|
3378
3556
|
|
|
3379
3557
|
const ZHI_CHONG = { 子: '午', 午: '子', 丑: '未', 未: '丑', 寅: '申', 申: '寅', 卯: '酉', 酉: '卯', 辰: '戌', 戌: '辰', 巳: '亥', 亥: '巳' };
|
|
@@ -3448,10 +3626,7 @@ function analyze(ke) {
|
|
|
3448
3626
|
}
|
|
3449
3627
|
|
|
3450
3628
|
function xunKong(dayGz, zhi) {
|
|
3451
|
-
|
|
3452
|
-
if (idx < 0) return null;
|
|
3453
|
-
const head = JIA_ZI_60[Math.floor(idx / 10) * 10];
|
|
3454
|
-
return (XUN_KONG[head] || []).includes(zhi) ? zhi : null;
|
|
3629
|
+
return xunKongOf(dayGz).includes(zhi) ? zhi : null;
|
|
3455
3630
|
}
|
|
3456
3631
|
|
|
3457
3632
|
function chuan(pos, zhi, tdp, dayGan, dayGz) {
|
|
@@ -3521,6 +3696,7 @@ function fanYin(ke, dayGan, dayZhi, stats, isFB) {
|
|
|
3521
3696
|
}
|
|
3522
3697
|
|
|
3523
3698
|
function buildDaliuren(input) {
|
|
3699
|
+
assertCivil(input);
|
|
3524
3700
|
const { year, month, day, hour = 0, minute = 0 } = input;
|
|
3525
3701
|
const lateZi = input.lateZi !== false;
|
|
3526
3702
|
const jdnBase = toJDN(year, month, day);
|
|
@@ -3618,6 +3794,8 @@ return { ZHI, GAN, TIAN_JIANG, JIANG_FULL, JIEQI_YUE_JIANG, GAN_JI_GONG, calcShe
|
|
|
3618
3794
|
__mods.hepan = (function () {
|
|
3619
3795
|
const { buildBazi } = __mods.bazi;
|
|
3620
3796
|
const { STEMS, BRANCHES } = __mods.calendar;
|
|
3797
|
+
const { assertCivil } = __mods.validate;
|
|
3798
|
+
const { SANHE_GROUPS } = __mods.sanhe;
|
|
3621
3799
|
|
|
3622
3800
|
const WX_GAN = { 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水' };
|
|
3623
3801
|
const WX_ZHI = { 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水' };
|
|
@@ -3628,7 +3806,6 @@ const LIU_HE = [['子', '丑'], ['寅', '亥'], ['卯', '戌'], ['辰', '酉'],
|
|
|
3628
3806
|
const CHONG = [['子', '午'], ['丑', '未'], ['寅', '申'], ['卯', '酉'], ['辰', '戌'], ['巳', '亥']];
|
|
3629
3807
|
const HAI = [['子', '未'], ['丑', '午'], ['寅', '巳'], ['卯', '辰'], ['申', '亥'], ['酉', '戌']];
|
|
3630
3808
|
const PO = [['子', '酉'], ['卯', '午'], ['辰', '丑'], ['未', '戌'], ['寅', '亥'], ['巳', '申']];
|
|
3631
|
-
const SAN_HE = [['申', '子', '辰'], ['寅', '午', '戌'], ['巳', '酉', '丑'], ['亥', '卯', '未']];
|
|
3632
3809
|
const XING = [['寅', '巳', '申'], ['丑', '戌', '未'], ['子', '卯']];
|
|
3633
3810
|
|
|
3634
3811
|
const pairIn = (list, a, b) => list.some((g) => g.includes(a) && g.includes(b) && a !== b);
|
|
@@ -3636,8 +3813,8 @@ const pairIn = (list, a, b) => list.some((g) => g.includes(a) && g.includes(b) &
|
|
|
3636
3813
|
function zhiRelation(a, b) {
|
|
3637
3814
|
if (a === b) return { name: '同支', score: 0, text: '日支相同,性情相近,容易互相理解,也容易同时卡在同一处' };
|
|
3638
3815
|
if (pairIn(LIU_HE, a, b)) return { name: '六合', score: 20, text: '日支六合,相处自然、彼此补位' };
|
|
3639
|
-
|
|
3640
|
-
|
|
3816
|
+
if (SANHE_GROUPS.some((g) => g.branches.includes(a) && g.branches.includes(b))) {
|
|
3817
|
+
return { name: '半合', score: 14, text: '日支同属三合局,易有共同语言' };
|
|
3641
3818
|
}
|
|
3642
3819
|
if (pairIn(CHONG, a, b)) return { name: '相冲', score: -15, text: '日支相冲,节奏与需求容易错位,需要刻意协调' };
|
|
3643
3820
|
if (pairIn(XING, a, b)) {
|
|
@@ -3659,6 +3836,8 @@ function wxToMe(other, me) {
|
|
|
3659
3836
|
}
|
|
3660
3837
|
|
|
3661
3838
|
function buildHepan(input) {
|
|
3839
|
+
assertCivil(input && input.a, 'a');
|
|
3840
|
+
assertCivil(input && input.b, 'b');
|
|
3662
3841
|
const A = buildBazi({ ...input.a, gender: input.a.gender });
|
|
3663
3842
|
const B = buildBazi({ ...input.b, gender: input.b.gender });
|
|
3664
3843
|
|
|
@@ -3754,6 +3933,7 @@ return { buildHepan };
|
|
|
3754
3933
|
|
|
3755
3934
|
__mods['ziwei-hepan'] = (function () {
|
|
3756
3935
|
const { buildZiwei } = __mods.ziwei;
|
|
3936
|
+
const { assertCivil } = __mods.validate;
|
|
3757
3937
|
|
|
3758
3938
|
const NAMES = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄', '迁移', '交友', '官禄', '田宅', '福德', '父母'];
|
|
3759
3939
|
const MUT_MEAN = {
|
|
@@ -3774,6 +3954,8 @@ function landOn(host, branch) {
|
|
|
3774
3954
|
}
|
|
3775
3955
|
|
|
3776
3956
|
function buildZiweiHepan(input) {
|
|
3957
|
+
assertCivil(input && input.a, 'a');
|
|
3958
|
+
assertCivil(input && input.b, 'b');
|
|
3777
3959
|
const A = buildZiwei({ ...input.a });
|
|
3778
3960
|
const B = buildZiwei({ ...input.b });
|
|
3779
3961
|
|
|
@@ -3869,6 +4051,7 @@ return { buildZiweiHepan };
|
|
|
3869
4051
|
__mods.qimen = (function () {
|
|
3870
4052
|
const { termInstantJd, termWindow, jdToParts, partsToJd, formatJd, ganzhiName, sexagenaryIndex, STEMS, BRANCHES } = __mods.calendar;
|
|
3871
4053
|
const { solarToLunar } = __mods.lunar;
|
|
4054
|
+
const { assertCivil } = __mods.validate;
|
|
3872
4055
|
|
|
3873
4056
|
const TIAN_GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
3874
4057
|
const DI_ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
@@ -3904,6 +4087,7 @@ const indexOfGz = (gz) => {
|
|
|
3904
4087
|
};
|
|
3905
4088
|
|
|
3906
4089
|
function buildSnapshot({ year, month, day, hour, minute, lateZiHourMode = 'same-day' }) {
|
|
4090
|
+
assertCivil({ year, month, day, hour, minute });
|
|
3907
4091
|
const jd = partsToJd({ year, month, day, hour, minute });
|
|
3908
4092
|
|
|
3909
4093
|
const baseJdn = partsToJd({ year, month, day, hour: 12, minute: 0 });
|
|
@@ -4158,6 +4342,7 @@ const findGong = (obj, val) =>
|
|
|
4158
4342
|
Object.keys(obj).find((k) => obj[k] === val || (val === '天芮' && obj[k] === '禽芮')) || '9';
|
|
4159
4343
|
|
|
4160
4344
|
function buildQimen(input) {
|
|
4345
|
+
if (!input.snapshot) assertCivil(input);
|
|
4161
4346
|
const snapshot = input.snapshot || buildSnapshot(input);
|
|
4162
4347
|
const hourIdx = input.hourIndex != null ? input.hourIndex : Math.floor((((input.hour ?? 0) + 1) % 24) / 2);
|
|
4163
4348
|
const hourGz = snapshot.hours[hourIdx] ? snapshot.hours[hourIdx].ganZhi : snapshot.hours[snapshot.hours.length - 1].ganZhi;
|
|
@@ -4676,6 +4861,455 @@ const TAROT_ELEMENT_FOCUS = {
|
|
|
4676
4861
|
return { TRIGRAM, YAO_POSITION, MOVING_NATURE, HEX64, HEX_BY_CORE, ZIWEI_STARS, WUXING_INDUSTRY, WUXING_ORGAN, TENGOD_STYLE, LIUREN_JIANG, DISCLAIMER, TAROT_ELEMENT_FOCUS };
|
|
4677
4862
|
})();
|
|
4678
4863
|
|
|
4864
|
+
__mods['interpret-plain'] = (function () {
|
|
4865
|
+
|
|
4866
|
+
const s = (v, fallback = '') => (v === undefined || v === null || v === '' ? fallback : String(v));
|
|
4867
|
+
const join = (arr) => (Array.isArray(arr) ? arr.filter(Boolean).join('') : '');
|
|
4868
|
+
const has = (v) => (Array.isArray(v) ? v.length > 0 : v !== undefined && v !== null && v !== '');
|
|
4869
|
+
|
|
4870
|
+
const STRENGTH_PLAIN = {
|
|
4871
|
+
身强: '底子偏足、抗压能力好,但容易自己给自己加码,学会收力比使劲更重要',
|
|
4872
|
+
偏强: '底子略足,扛得住事,也容易把事都往自己身上揽',
|
|
4873
|
+
中和: '力量比较匀,属于「给什么环境就长出什么样子」的类型',
|
|
4874
|
+
偏弱: '底子偏薄,靠外部支援比硬扛有效,选对圈子与节奏很关键',
|
|
4875
|
+
身弱: '底子偏薄,先修恢复力再谈冲刺,硬撑的性价比最低',
|
|
4876
|
+
};
|
|
4877
|
+
|
|
4878
|
+
const DIR_SECTION = {
|
|
4879
|
+
wealth: '「工作与职业选择」与「时间窗」',
|
|
4880
|
+
career: '「事业发展」与「时间窗」',
|
|
4881
|
+
love: '「爱情与感情」',
|
|
4882
|
+
health: '「健康状况」',
|
|
4883
|
+
study: '「事业发展」与「时间窗」',
|
|
4884
|
+
travel: '「整体运势」与「时间窗」',
|
|
4885
|
+
general: '「整体运势」与「看盘顺序」',
|
|
4886
|
+
};
|
|
4887
|
+
|
|
4888
|
+
function buildAnswer(r, ask, ctx = {}) {
|
|
4889
|
+
if (!r || !r.system) return '';
|
|
4890
|
+
const q = s(ask && ask.question).trim();
|
|
4891
|
+
const dir = (ask && ask.direction) || 'general';
|
|
4892
|
+
const where = r.system === 'bazi'
|
|
4893
|
+
? (DIR_SECTION[dir] || DIR_SECTION.general)
|
|
4894
|
+
: '下面与你的问题最贴近的那一节';
|
|
4895
|
+
const head = q ? `你问的是「${q}」。` : '';
|
|
4896
|
+
|
|
4897
|
+
if (r.system === 'bazi') {
|
|
4898
|
+
const dm = r.dayMaster || {};
|
|
4899
|
+
const st = r.strength || {};
|
|
4900
|
+
const ys = (r.frame && r.frame.yongshen) || {};
|
|
4901
|
+
const favor = join(ys.favor);
|
|
4902
|
+
const avoid = join(ys.avoid);
|
|
4903
|
+
const ln = (r.liunian || [])[0];
|
|
4904
|
+
const parts = [
|
|
4905
|
+
head + `先说结论:你是${s(dm.stem)}${s(dm.element)}日主,命局${s(st.level, '待定')}` +
|
|
4906
|
+
`${favor ? `,顺${favor}、忌${avoid}——往${favor}的方向安排事情会更省力` : ''}。`,
|
|
4907
|
+
ln && ln.ganzhi
|
|
4908
|
+
? `${s(ln.year)} 年是${ln.ganzhi}年(${s(ln.luck && ln.luck.tier, '平')}),这是「大环境」这一层。`
|
|
4909
|
+
: '',
|
|
4910
|
+
`具体到你问的这件事,重点看${where}。`,
|
|
4911
|
+
];
|
|
4912
|
+
return parts.filter(Boolean).join('');
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
if (r.system === 'ziwei') {
|
|
4916
|
+
const soul = r.soul || {};
|
|
4917
|
+
const body = r.body || {};
|
|
4918
|
+
const bureau = r.bureau || {};
|
|
4919
|
+
const md = r.lifeMaster || '';
|
|
4920
|
+
return [
|
|
4921
|
+
head + `先说结论:你的命宫落${s(soul.branch, '—')}宫` +
|
|
4922
|
+
`${bureau.name ? `、${bureau.name}` : ''}${md ? `,命主${md}` : ''}` +
|
|
4923
|
+
`${body.lands ? `,身宫落在「${body.lands}」——这张盘的重心在这件事上` : ''}。`,
|
|
4924
|
+
`具体到你问的这件事,重点看${where}。`,
|
|
4925
|
+
].join('');
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
if (r.system === 'liuyao') {
|
|
4929
|
+
const ben = r.ben || {};
|
|
4930
|
+
const moving = (r.cast && r.cast.movingPositions) || [];
|
|
4931
|
+
const bian = r.bian || {};
|
|
4932
|
+
return [
|
|
4933
|
+
head + `先说结论:本卦是${s(ben.name, '—')}` +
|
|
4934
|
+
`${ben.palace ? `(${ben.palace})` : ''}` +
|
|
4935
|
+
`${moving.length ? `,第 ${moving.join('、')} 爻动,变为${s(bian.name, '—')}` : ',六爻安静没有动爻'}。`,
|
|
4936
|
+
'断卦的落点在「世爻」(代表你)和「动爻」(事情的推动点),下面「看盘顺序」就按这个次序展开。',
|
|
4937
|
+
].join('');
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
return head ? `${head}下面按五个维度拆开看,重点看${where}。` : `下面按五个维度拆开看,重点看${where}。`;
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4943
|
+
function buildPlain(r, ask, ctx = {}) {
|
|
4944
|
+
if (!r || !r.system) return '';
|
|
4945
|
+
if (r.system === 'bazi') {
|
|
4946
|
+
const st = r.strength || {};
|
|
4947
|
+
const ys = (r.frame && r.frame.yongshen) || {};
|
|
4948
|
+
const favor = join(ys.favor);
|
|
4949
|
+
const avoid = join(ys.avoid);
|
|
4950
|
+
const strength = STRENGTH_PLAIN[st.level] || '力量偏中性';
|
|
4951
|
+
const tiaohou = ys.tiaohou && ys.tiaohou.note ? `另外,${ys.tiaohou.note},所以「暖一点、亮一点」的环境通常更舒服。` : '';
|
|
4952
|
+
return `你的命局属于「${s(st.level, '中性')}」:${strength}。` +
|
|
4953
|
+
`${favor ? `能补${favor}的环境(行业、方位、作息、相处的人)会让你省力;${avoid ? `而${avoid}偏重的场合容易空耗,能绕就绕。` : ''}` : ''}` +
|
|
4954
|
+
tiaohou;
|
|
4955
|
+
}
|
|
4956
|
+
if (r.system === 'ziwei') {
|
|
4957
|
+
const soul = r.soul || {};
|
|
4958
|
+
const body = r.body || {};
|
|
4959
|
+
return `紫微看的是「你与环境怎么互动」。命宫落${s(soul.branch, '—')}宫,说明你做事的第一反应是从这个宫位的气质出发` +
|
|
4960
|
+
`${body.lands ? `;身宫在「${body.lands}」,意味着真正让你上心的,是${body.lands}这件事` : ''}。`;
|
|
4961
|
+
}
|
|
4962
|
+
if (r.system === 'liuyao') {
|
|
4963
|
+
const ben = r.ben || {};
|
|
4964
|
+
const moving = (r.cast && r.cast.movingPositions) || [];
|
|
4965
|
+
return `${s(ben.name, '这一卦')}讲的是一个「${moving.length ? '正在动、还会变' : '暂时不动、先看清'}」的局面——` +
|
|
4966
|
+
`${moving.length ? '有动爻,说明事情还有转圜空间,不是死局。' : '六爻安静,说明当下更该观察而不是硬推。'}`;
|
|
4967
|
+
}
|
|
4968
|
+
return '';
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
function dimensionPlain(key, r, ctx = {}) {
|
|
4972
|
+
if (!r || r.system !== 'bazi') return '';
|
|
4973
|
+
const ys = (r.frame && r.frame.yongshen) || {};
|
|
4974
|
+
const favor = join(ys.favor);
|
|
4975
|
+
const avoid = join(ys.avoid);
|
|
4976
|
+
const st = r.strength || {};
|
|
4977
|
+
if (key === 'overall') return `一句话:底子${s(st.level, '中性')},往${favor || '有利方向'}走省力。`;
|
|
4978
|
+
if (key === 'work') return `一句话:挑${favor || '喜用'}属性的活儿,比硬碰${avoid || '忌神'}省力。`;
|
|
4979
|
+
if (key === 'career') return `一句话:你的上升方式,偏向把${favor || '长处'}做成看得见的成果。`;
|
|
4980
|
+
if (key === 'love') return '一句话:关系里先看清自己要什么,再决定推进速度。';
|
|
4981
|
+
if (key === 'health') return '一句话:作息稳定比任何调理都优先。';
|
|
4982
|
+
return '';
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
const GLOSSARY = [
|
|
4986
|
+
['日主', '你出生那天对应的天干,代表「你自己」,整张盘都围着它转。'],
|
|
4987
|
+
['用神', '这张盘里最需要补的那一类力量,顺着它做事更省力。'],
|
|
4988
|
+
['忌神', '补得越多越别扭的那一类力量,不是坏东西,只是对你性价比低。'],
|
|
4989
|
+
['调候', '按出生季节调温补缺:冬天的盘怕冷,夏天的盘怕燥。'],
|
|
4990
|
+
['格局', '把整张盘的结构归成一个类型,用来看「你适合怎么打」。'],
|
|
4991
|
+
['十神', '把五行关系翻译成人事:财=资源、官杀=规则与压力、印=支持、食伤=表达、比劫=同伴。'],
|
|
4992
|
+
['月令', '出生的月份,是判断强弱最关键的一个位置。'],
|
|
4993
|
+
['日支', '日柱的地支,也叫「配偶宫」,看亲密关系的第一落点。'],
|
|
4994
|
+
['大运', '十年一换的大阶段,决定这十年顺不顺手。'],
|
|
4995
|
+
['流年', '每一年的干支,看当年的具体环境。'],
|
|
4996
|
+
['空亡', '落到空亡的字,力量打折,事情容易「看着有、抓不住」。'],
|
|
4997
|
+
['伏神', '卦里没出现的六亲,藏在同宫首卦的对应爻下,用时才看得见。'],
|
|
4998
|
+
['世应', '世爻代表你,应爻代表对方或外部环境。'],
|
|
4999
|
+
['动爻', '会变的爻,是事情「往哪走」的推动点。'],
|
|
5000
|
+
['纳甲', '给六个爻配上天干地支,六亲与生克才有依据。'],
|
|
5001
|
+
['四化', '禄权科忌四种变化,标记星曜在这一步运里扮演什么角色。'],
|
|
5002
|
+
['大限', '紫微里的十年阶段,作用类似八字的「大运」。'],
|
|
5003
|
+
];
|
|
5004
|
+
|
|
5005
|
+
const GLOSSARY_BY_SYSTEM = {
|
|
5006
|
+
liuyao: {
|
|
5007
|
+
用神: '你问的这件事对应的那一类六亲,断卦先把它找出来。',
|
|
5008
|
+
月令: '起卦时的月份,用来看各爻旺不旺。',
|
|
5009
|
+
日支: '起卦当天的地支,与月建一起决定爻的力量。',
|
|
5010
|
+
大运: '六爻不看大运,这里只取当前这一件事的象。',
|
|
5011
|
+
流年: '六爻一般不看流年,看的是眼下这一卦的时段。',
|
|
5012
|
+
},
|
|
5013
|
+
ziwei: {
|
|
5014
|
+
用神: '紫微斗数以「用神宫」的强弱看事情,不取五行喜忌那套。',
|
|
5015
|
+
日主: '紫微斗数不看日主,看的是命宫与星曜组合。',
|
|
5016
|
+
十神: '紫微斗数用四化与星情,不用十神。',
|
|
5017
|
+
},
|
|
5018
|
+
};
|
|
5019
|
+
|
|
5020
|
+
function buildGlossary(texts, system) {
|
|
5021
|
+
const body = (Array.isArray(texts) ? texts : [texts]).filter(Boolean).join('');
|
|
5022
|
+
if (!body) return [];
|
|
5023
|
+
const override = GLOSSARY_BY_SYSTEM[system] || {};
|
|
5024
|
+
return GLOSSARY
|
|
5025
|
+
.filter(([term]) => body.includes(term))
|
|
5026
|
+
.map(([term, plain]) => ({ term, plain: override[term] || plain }))
|
|
5027
|
+
.slice(0, 10);
|
|
5028
|
+
}
|
|
5029
|
+
|
|
5030
|
+
const FOLLOWUPS = {
|
|
5031
|
+
career: ['这份工作要不要动,明年哪几个月是窗口?', '和上级的关系该硬碰还是绕开?', '现在转方向,代价主要在哪儿?'],
|
|
5032
|
+
wealth: ['这笔钱现在投出去合适吗?', '我的财主要来自工资还是副业?', '今年哪段时间最容易破财?'],
|
|
5033
|
+
love: ['这段关系能走到哪一步?', '什么时候适合把关系定下来?', '我在关系里最容易忽略什么?'],
|
|
5034
|
+
health: ['哪段时间身体最需要留意?', '作息怎么调最划算?', '查体和作息之外,还有什么是能自己控制的?'],
|
|
5035
|
+
study: ['今年再考一次,胜算在哪、短板在哪?', '复习节奏该怎么排?', '要不要换目标院校或岗位?'],
|
|
5036
|
+
travel: ['这次出门顺不顺,哪天走更好?', '换个城市生活,合不合适?'],
|
|
5037
|
+
general: ['今年最该抓的一件事是什么?', '最该避开的坑在哪?', '接下来三个月,什么时间点值得留意?'],
|
|
5038
|
+
};
|
|
5039
|
+
|
|
5040
|
+
function buildFollowups(r, ask) {
|
|
5041
|
+
const dir = (ask && ask.direction) || 'general';
|
|
5042
|
+
const list = FOLLOWUPS[dir] || FOLLOWUPS.general;
|
|
5043
|
+
return list.slice(0, 3);
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
return { buildAnswer, buildPlain, dimensionPlain, buildGlossary, buildFollowups };
|
|
5047
|
+
})();
|
|
5048
|
+
|
|
5049
|
+
__mods['interpret-knowledge'] = (function () {
|
|
5050
|
+
|
|
5051
|
+
const KNOWLEDGE = [
|
|
5052
|
+
{
|
|
5053
|
+
id: 'bazi-yueling-siquan',
|
|
5054
|
+
system: 'bazi', dims: ['overall', 'career'],
|
|
5055
|
+
gist: '月令不是「一个月」,是当月最有权的那一个字。',
|
|
5056
|
+
detail: '月令藏干按本气、中气、余气分日用事,本气当权的时间最长。所以判旺衰先看日主在月令是得令还是失令——得令者未看全局,已有三分底气。',
|
|
5057
|
+
source: '《子平真诠》论用神;人元司权分日之说见《渊海子平》',
|
|
5058
|
+
status: 'variant',
|
|
5059
|
+
note: '各家对中气、余气各主几日分法不同(有 7/7/16、9/3/18 等多说),本插件只取「本气当权」这一层,未细分日数。',
|
|
5060
|
+
},
|
|
5061
|
+
{
|
|
5062
|
+
id: 'bazi-tonggen-tougan',
|
|
5063
|
+
system: 'bazi', dims: ['overall', 'career'],
|
|
5064
|
+
gist: '天干是露在外面的样子,地支是藏在地下的根。',
|
|
5065
|
+
detail: '同一个五行,透在天干叫「显」,扎在地支叫「有根」。只有天干没有地支,是看着有、使不上劲;只有地支不通天干,是有实力、不显眼。判断一个人能不能扛事,看的就是根有多深。',
|
|
5066
|
+
source: '《滴天髓》「天干得一比肩,不如得支中一墓库」一路的取象',
|
|
5067
|
+
status: 'settled',
|
|
5068
|
+
},
|
|
5069
|
+
{
|
|
5070
|
+
id: 'bazi-wangxiang',
|
|
5071
|
+
system: 'bazi', dims: ['overall', 'health'],
|
|
5072
|
+
gist: '旺相休囚死是五行在当月的五种状态,不是吉凶等级。',
|
|
5073
|
+
detail: '以月令五行为基准:当令者旺、我生者相、生我者休、克我者囚、我克者死。它是「此刻谁说话有分量」的相对排序,换个季节就整体重排,不能当固定评价。',
|
|
5074
|
+
source: '《五行大义》旺相休囚死;历代命书沿用',
|
|
5075
|
+
status: 'settled',
|
|
5076
|
+
},
|
|
5077
|
+
{
|
|
5078
|
+
id: 'bazi-shensha',
|
|
5079
|
+
system: 'bazi', dims: ['overall', 'love'],
|
|
5080
|
+
gist: '神煞是标签,不是判决。',
|
|
5081
|
+
detail: '神煞给盘面加注脚(如「这个组合常被叫作天乙贵人」),吉凶仍要看格局、旺衰、行运三件事。单见一颗凶煞不足以断凶,单见一颗吉星也不足以断吉——这是历代命书反复强调、而市面软件常忽略的一条。',
|
|
5082
|
+
source: '《三命通会》神煞篇;《子平真诠》力主「神煞不主吉凶」',
|
|
5083
|
+
status: 'settled',
|
|
5084
|
+
},
|
|
5085
|
+
{
|
|
5086
|
+
id: 'bazi-geju',
|
|
5087
|
+
system: 'bazi', dims: ['career'],
|
|
5088
|
+
gist: '先看月令能不能取格,取不到才论从格、化气。',
|
|
5089
|
+
detail: '月令有可用之神(透干或会支有力),按正格论;月令无用、全局一边倒,才考虑从格;日干与相邻天干五合、且月令有化神之气,才论化气格。顺序颠倒会把普通格局误判成特殊格局。',
|
|
5090
|
+
source: '《子平真诠》「论用神」;从化条件见《滴天髓》',
|
|
5091
|
+
status: 'variant',
|
|
5092
|
+
note: '化气格成立条件各家宽严不一(是否要求月令为化神、是否允许有根),本插件按「有合且月令得化神之气」从严处理。',
|
|
5093
|
+
},
|
|
5094
|
+
{
|
|
5095
|
+
id: 'bazi-tiaohou',
|
|
5096
|
+
system: 'bazi', dims: ['overall', 'health'],
|
|
5097
|
+
gist: '冬夏生人先调温,其余先看扶抑。',
|
|
5098
|
+
detail: '调候是针对出生季节的温度校正:冬生水冷木寒,先要火来解冻;夏生火炎土燥,先要水来润泽。调候与扶抑冲突时,多数命书主张先调候,因为寒暖失衡时,再怎么补强弱也难受。',
|
|
5099
|
+
source: '《穷通宝鉴》(拦江网)调候用神体系',
|
|
5100
|
+
status: 'variant',
|
|
5101
|
+
note: '调候与扶抑的优先级各家有争论,也有主张以扶抑为主、调候为辅的。本插件同时给出两者,不替你定谁优先。',
|
|
5102
|
+
},
|
|
5103
|
+
{
|
|
5104
|
+
id: 'bazi-suiyun',
|
|
5105
|
+
system: 'bazi', dims: ['overall', 'career'],
|
|
5106
|
+
gist: '大运管十年基调,流年管当年触发。',
|
|
5107
|
+
detail: '大运像换了条路,流年像这条路上某一段的天气。岁运并临(大运与流年干支相同)与伏吟反吟(干支重复或与原局对冲)是传统上标记「变动较大」的年份,属于提示而非断语。',
|
|
5108
|
+
source: '岁运并临、伏吟反吟见《三命通会》《渊海子平》',
|
|
5109
|
+
status: 'variant',
|
|
5110
|
+
note: '「岁运并临主大凶」是流传最广也最被滥用的说法,本插件只作变动提示,不断吉凶。',
|
|
5111
|
+
},
|
|
5112
|
+
{
|
|
5113
|
+
id: 'bazi-liuqin',
|
|
5114
|
+
system: 'bazi', dims: ['love'],
|
|
5115
|
+
gist: '六亲有「星」也有「宫」,两处一起看才立体。',
|
|
5116
|
+
detail: '星是十神(男命财为妻、女命官杀为夫、印为母一类),宫是四柱位置(年月日时对应祖上、父母、配偶、子女)。星宫同参:星现而宫破,缘分有但过程有阻;宫稳而星不现,关系平淡而少波折。',
|
|
5117
|
+
source: '星宫同参见《子平真诠》《滴天髓》',
|
|
5118
|
+
status: 'variant',
|
|
5119
|
+
note: '女命以官杀为夫是主流,亦有以日支为主、官杀为辅的取法;本插件按主流取,并在文案中说明。',
|
|
5120
|
+
},
|
|
5121
|
+
{
|
|
5122
|
+
id: 'bazi-kongwang',
|
|
5123
|
+
system: 'bazi', dims: ['overall'],
|
|
5124
|
+
gist: '空亡不是「没有」,是力道打折、抓不实。',
|
|
5125
|
+
detail: '日柱所在旬之外的两个地支为旬空。落空之支力量减弱,但逢冲、逢合、逢填实之期可以「出空」,届时又恢复作用。所以空亡要看它是否被救,不宜一见到空就断无。',
|
|
5126
|
+
source: '旬空出空之说见《渊海子平》;六爻《卜筮正宗》用法相通',
|
|
5127
|
+
status: 'variant',
|
|
5128
|
+
note: '空亡是否逢空即空,各家分歧较大,本插件只标注空亡,不做吉凶推断。',
|
|
5129
|
+
},
|
|
5130
|
+
{
|
|
5131
|
+
id: 'bazi-xingchong',
|
|
5132
|
+
system: 'bazi', dims: ['overall', 'love'],
|
|
5133
|
+
gist: '合能解冲,冲也能破合——关系不是单向的。',
|
|
5134
|
+
detail: '刑冲合害要按力的大小与先后比:两支相冲,若有第三支与其中一支相合,冲力被牵制;反之先合后冲,合局也会被冲散。三合成局通常要求月令或日主参与,否则只是拱合,力量有限。',
|
|
5135
|
+
source: '《三命通会》论刑冲合害',
|
|
5136
|
+
status: 'settled',
|
|
5137
|
+
},
|
|
5138
|
+
|
|
5139
|
+
{
|
|
5140
|
+
id: 'ziwei-sanfang',
|
|
5141
|
+
system: 'ziwei', dims: ['overall', 'career'],
|
|
5142
|
+
gist: '看一个宫,要看它对面和左右三个宫。',
|
|
5143
|
+
detail: '三方四正=本宫加对宫、三合两宫。命宫的三方是财帛、官禄、迁移,代表钱、事、外出的环境;只看本宫会漏掉一半信息,因为紫微断的是宫与宫之间的互动,不是单宫星曜的堆叠。',
|
|
5144
|
+
source: '《紫微斗数全书》三方四正说',
|
|
5145
|
+
status: 'settled',
|
|
5146
|
+
},
|
|
5147
|
+
{
|
|
5148
|
+
id: 'ziwei-sihua',
|
|
5149
|
+
system: 'ziwei', dims: ['overall', 'career'],
|
|
5150
|
+
gist: '四化是变化的方向,不是吉凶等级。',
|
|
5151
|
+
detail: '生年四化定先天基调(禄主得、权主掌、科主名、忌主执),宫干四化定后天演变;自化是宫干化出的星就在本宫,主此宫之事自己会起变化。同一个忌,落在六亲宫与落在财帛宫,含义完全不同。',
|
|
5152
|
+
source: '《紫微斗数全书》四化篇;宫干飞化见飞星派',
|
|
5153
|
+
status: 'variant',
|
|
5154
|
+
note: '三合派重星情与格局、飞星派重宫干飞化,两派读法差异明显。本插件给出生年四化与宫干自化,未做完整飞星串连。',
|
|
5155
|
+
},
|
|
5156
|
+
{
|
|
5157
|
+
id: 'ziwei-miaowang',
|
|
5158
|
+
system: 'ziwei', dims: ['overall'],
|
|
5159
|
+
gist: '庙旺得利平不陷,说的是星在这个宫舒不舒服。',
|
|
5160
|
+
detail: '同一颗星落在不同宫位,发挥程度差别很大。庙旺是得地,平陷是不得地;不得地不等于不好,只是这颗星的长处不容易发挥,需要借三方吉星来补。',
|
|
5161
|
+
source: '《紫微斗数全书》诸星庙旺表',
|
|
5162
|
+
status: 'variant',
|
|
5163
|
+
note: '太阳、太阴在少数宫位的庙旺标注各家有出入;本插件只标十四主星,不硬标辅星。',
|
|
5164
|
+
},
|
|
5165
|
+
{
|
|
5166
|
+
id: 'ziwei-daxian',
|
|
5167
|
+
system: 'ziwei', dims: ['career'],
|
|
5168
|
+
gist: '大限定十年主题,流年定当年事件。',
|
|
5169
|
+
detail: '大限看这十年主要在忙什么,流年看其中某一年被什么触发。两者的四化会叠在同一些宫位上,叠加处就是这十年里的关键点——只看流年而不看大限,容易把小事放大。',
|
|
5170
|
+
source: '《紫微斗数全书》大限流年篇',
|
|
5171
|
+
status: 'settled',
|
|
5172
|
+
},
|
|
5173
|
+
{
|
|
5174
|
+
id: 'ziwei-shengong',
|
|
5175
|
+
system: 'ziwei', dims: ['overall'],
|
|
5176
|
+
gist: '命宫是「你本来是谁」,身宫是「你后来把力气放在哪」。',
|
|
5177
|
+
detail: '身宫不独立成宫,而是寄在某一宫里(如寄财帛、寄官禄)。它标出中年之后的着力点:命宫强而身宫弱,多为主观强但落地慢;身宫得地,做事更容易见效。',
|
|
5178
|
+
source: '《紫微斗数全书》命身宫说',
|
|
5179
|
+
status: 'variant',
|
|
5180
|
+
note: '身宫寄宫的取法各家一致,但身宫与命宫谁更重,各家侧重不同。',
|
|
5181
|
+
},
|
|
5182
|
+
{
|
|
5183
|
+
id: 'ziwei-kongjie',
|
|
5184
|
+
system: 'ziwei', dims: ['overall', 'career'],
|
|
5185
|
+
gist: '空劫主计划外的变动,遇禄则财来财去。',
|
|
5186
|
+
detail: '地空地劫在紫微里代表落空与抽离:落财帛多主钱财不易积累,落官禄多主方向反复。若与禄存、化禄同宫,常见进得快、出得也快;与忌同宫则容易因变动而受挫。',
|
|
5187
|
+
source: '《紫微斗数全书》空劫条',
|
|
5188
|
+
status: 'variant',
|
|
5189
|
+
note: '空劫是否见禄反吉,各家说法不一,本插件只提示变动性质,不判吉凶。',
|
|
5190
|
+
},
|
|
5191
|
+
|
|
5192
|
+
{
|
|
5193
|
+
id: 'liuyao-yongshen',
|
|
5194
|
+
system: 'liuyao', dims: ['overall'],
|
|
5195
|
+
gist: '问什么,就以那一类六亲为用神。',
|
|
5196
|
+
detail: '问财看妻财、问事业官位看官鬼、问文书房产看父母、问子女下属看子孙、问同辈朋友看兄弟。用神两现时,取动者、取临世者、取旺者;用神不上卦,则从本宫首卦取伏神。这一步定错,后面全盘白算。',
|
|
5197
|
+
source: '《卜筮正宗》用神章;《增删卜易》同',
|
|
5198
|
+
status: 'settled',
|
|
5199
|
+
},
|
|
5200
|
+
{
|
|
5201
|
+
id: 'liuyao-wangshuai',
|
|
5202
|
+
system: 'liuyao', dims: ['overall'],
|
|
5203
|
+
gist: '月建管当令,日辰管生杀。',
|
|
5204
|
+
detail: '月建决定这一爻在这个月有没有气;日辰能生能克能冲能合,作用更直接。月破之爻当月无力;日冲则看动静——静爻被日冲为暗动,动爻被日冲为冲散。',
|
|
5205
|
+
source: '《卜筮正宗》月建日辰章',
|
|
5206
|
+
status: 'settled',
|
|
5207
|
+
},
|
|
5208
|
+
{
|
|
5209
|
+
id: 'liuyao-dongbian',
|
|
5210
|
+
system: 'liuyao', dims: ['overall'],
|
|
5211
|
+
gist: '动爻是「事情自己在动」的那一处。',
|
|
5212
|
+
detail: '动爻能生克静爻,静爻不能反克动爻。变爻对动爻有回头生、回头克两种反作用;化进神(如寅化卯)主加强,化退神(如卯化寅)主减弱。一爻独发看此爻,多爻乱动则看用神受不受伤。',
|
|
5213
|
+
source: '《卜筮正宗》动变章;《增删卜易》进神退神',
|
|
5214
|
+
status: 'settled',
|
|
5215
|
+
},
|
|
5216
|
+
{
|
|
5217
|
+
id: 'liuyao-fushen',
|
|
5218
|
+
system: 'liuyao', dims: ['overall'],
|
|
5219
|
+
gist: '伏神是藏在下面、要借力才出得来的那一爻。',
|
|
5220
|
+
detail: '用神不上卦,就从本宫首卦的同位取伏神。伏神要得出——得日月生扶、或飞神生伏神、或伏神自身旺相,才有用;伏在忌神之下且被克,则虽有如无。',
|
|
5221
|
+
source: '《卜筮正宗》伏神章',
|
|
5222
|
+
status: 'settled',
|
|
5223
|
+
},
|
|
5224
|
+
{
|
|
5225
|
+
id: 'liuyao-yingqi',
|
|
5226
|
+
system: 'liuyao', dims: ['overall', 'career'],
|
|
5227
|
+
gist: '应期是「什么时候见分晓」,不是「哪天」。',
|
|
5228
|
+
detail: '近事以日计、远事以月计;常见取法是等用神逢值、逢冲、逢合之期,或等忌神受制之期。应期法各家互有出入,同一卦不同取法可以差出很远,所以只作时间窗口的提示。',
|
|
5229
|
+
source: '《卜筮正宗》应期章;《增删卜易》',
|
|
5230
|
+
status: 'variant',
|
|
5231
|
+
note: '应期取法(值日、冲、合、填实、旬空出空)各家优先级不同,本插件不做具体日期推算。',
|
|
5232
|
+
},
|
|
5233
|
+
{
|
|
5234
|
+
id: 'liuyao-liushen',
|
|
5235
|
+
system: 'liuyao', dims: ['overall', 'love'],
|
|
5236
|
+
gist: '六神是这件事的味道,不是主角。',
|
|
5237
|
+
detail: '青龙主喜庆与正事、朱雀主口舌与文书、勾陈主田土与迟滞、螣蛇主虚惊与缠绕、白虎主伤损与急迫、玄武主暗昧与失脱。六神只作取象的修饰,断吉凶仍以六亲旺衰生克为主。',
|
|
5238
|
+
source: '《卜筮正宗》六神章',
|
|
5239
|
+
status: 'settled',
|
|
5240
|
+
},
|
|
5241
|
+
|
|
5242
|
+
{
|
|
5243
|
+
id: 'meihua-tiyong',
|
|
5244
|
+
system: 'meihua', dims: ['overall'],
|
|
5245
|
+
gist: '动的那一卦为用,不动的那一卦为体。',
|
|
5246
|
+
detail: '体代表自己、用代表所问之事。体克用主可控、用克体主受制、用生体主得助、体生用主付出。再看互卦与变卦判断中段与结局——梅花只取象不排盘,落点比六爻粗。',
|
|
5247
|
+
source: '《梅花易数》体用篇',
|
|
5248
|
+
status: 'settled',
|
|
5249
|
+
},
|
|
5250
|
+
{
|
|
5251
|
+
id: 'daliuren-sike',
|
|
5252
|
+
system: 'daliuren', dims: ['overall'],
|
|
5253
|
+
gist: '四课看事从哪来,三传看事往哪去。',
|
|
5254
|
+
detail: '四课由日干日支的阴阳与天地盘相叠而得;三传依九宗门(贼克、比用、涉害、遥克等)取初传,再顺推中传末传。三传之间的生克与空亡,是判断事情走向的主线。',
|
|
5255
|
+
source: '《大六壬大全》;九宗门取传法',
|
|
5256
|
+
status: 'settled',
|
|
5257
|
+
},
|
|
5258
|
+
{
|
|
5259
|
+
id: 'qimen-yinyang',
|
|
5260
|
+
system: 'qimen', dims: ['overall'],
|
|
5261
|
+
gist: '冬至后阳遁、夏至后阴遁,局数由节气与三元定。',
|
|
5262
|
+
detail: '一年分阴阳两遁,各九局;局数取决于当前节气与上中下三元(符头定元)。取局流派有拆补、超神接气、置闰三种,同一时刻可能排出不同局数——这不是算错,是流派差异。',
|
|
5263
|
+
source: '《烟波钓叟歌》;拆补与置闰见《奇门遁甲统宗》',
|
|
5264
|
+
status: 'variant',
|
|
5265
|
+
note: '三种取局法在节气交接前后差异最明显,本插件同时实现三种,可在参数里指定。',
|
|
5266
|
+
},
|
|
5267
|
+
{
|
|
5268
|
+
id: 'tarot-orientation',
|
|
5269
|
+
system: 'tarot', dims: ['overall'],
|
|
5270
|
+
gist: '逆位常见的两种读法:削弱,或转向内。',
|
|
5271
|
+
detail: '一派认为逆位是正位力量的减弱与阻塞;另一派主张逆位是同一原型的内向化、未表达。同一张逆位牌,两种读法给出的建议方向可能相反,所以牌阵解读要先把口径讲清楚。',
|
|
5272
|
+
source: '韦特体系传统;逆位读法属近代流派分歧',
|
|
5273
|
+
status: 'variant',
|
|
5274
|
+
note: '逆位是否参与解读本身就有分歧(有牌阵传统完全不用逆位)。本插件按削弱加内向综合取象,并在开头声明。',
|
|
5275
|
+
},
|
|
5276
|
+
{
|
|
5277
|
+
id: 'liuyao-yingqi-exact',
|
|
5278
|
+
system: 'liuyao', dims: [],
|
|
5279
|
+
gist: '具体应期(哪一天应验)本插件尚未推算。',
|
|
5280
|
+
detail: '应期需要综合用神旺衰、动变、旬空出空、日月冲合多层条件,且各家取法不同。当前只给时间窗口的方向性提示,不给具体日期——与其给一个看起来精确、实则拍脑袋的日子,不如留白。',
|
|
5281
|
+
source: '待补:《卜筮正宗》应期章逐条实现 + 138 条卦例对拍',
|
|
5282
|
+
status: 'todo',
|
|
5283
|
+
note: '扩展方向:用《卜筮正宗》卦例里有明确应验日期的条目做回归,先验证再输出。',
|
|
5284
|
+
},
|
|
5285
|
+
{
|
|
5286
|
+
id: 'bazi-dayun-jiaoqi',
|
|
5287
|
+
system: 'bazi', dims: [],
|
|
5288
|
+
gist: '起运时间按「三日折一年」折算,未做交运日的时辰级校正。',
|
|
5289
|
+
detail: '传统折算为三天折一年、一天折四月、一时辰折十天。本插件按此折算到年月,不含交运当日的时辰细分,因此起运年份在边界处可能相差一年内。',
|
|
5290
|
+
source: '《渊海子平》起运法;交运日细算各家不一',
|
|
5291
|
+
status: 'todo',
|
|
5292
|
+
note: '扩展方向:补交运日的时辰级推算,并用已知案例核对。',
|
|
5293
|
+
},
|
|
5294
|
+
];
|
|
5295
|
+
|
|
5296
|
+
function knowledgeFor(system, dimKey) {
|
|
5297
|
+
return KNOWLEDGE.filter((k) => k.system === system && k.dims.includes(dimKey));
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
function caveatsFor(system) {
|
|
5301
|
+
return KNOWLEDGE.filter((k) => k.system === system && k.status !== 'settled');
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5304
|
+
const STATUS_LABEL = {
|
|
5305
|
+
settled: '诸家一致',
|
|
5306
|
+
variant: '各家有异说',
|
|
5307
|
+
todo: '待核实',
|
|
5308
|
+
};
|
|
5309
|
+
|
|
5310
|
+
return { KNOWLEDGE, knowledgeFor, caveatsFor, STATUS_LABEL };
|
|
5311
|
+
})();
|
|
5312
|
+
|
|
4679
5313
|
__mods.interpret = (function () {
|
|
4680
5314
|
|
|
4681
5315
|
const { HEX64, HEX_BY_CORE, TRIGRAM, YAO_POSITION, MOVING_NATURE, ZIWEI_STARS, WUXING_INDUSTRY, WUXING_ORGAN, TENGOD_STYLE, LIUREN_JIANG, TAROT_ELEMENT_FOCUS, DISCLAIMER } = __mods['interpret-data'];
|
|
@@ -4815,6 +5449,7 @@ function baziInterpret(r) {
|
|
|
4815
5449
|
const primary = pick(frame, 'primaryName', '未取定');
|
|
4816
5450
|
const gender = pick(r.input, 'gender', '');
|
|
4817
5451
|
const isMale = gender === 'male';
|
|
5452
|
+
const spouseKey = isMale ? '财' : '官杀';
|
|
4818
5453
|
|
|
4819
5454
|
const age = Number(pick(r.liunian?.[0], 'age', NaN));
|
|
4820
5455
|
const list = Array.isArray(r.dayun?.list) ? r.dayun.list : [];
|
|
@@ -5006,15 +5641,44 @@ function baziInterpret(r) {
|
|
|
5006
5641
|
key: 'geju', label: '格局依据',
|
|
5007
5642
|
text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
|
|
5008
5643
|
},
|
|
5644
|
+
{
|
|
5645
|
+
key: 'quant', label: '量化口径',
|
|
5646
|
+
text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`,
|
|
5647
|
+
},
|
|
5009
5648
|
{
|
|
5010
5649
|
key: 'limits', label: '未纳入',
|
|
5011
5650
|
text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用以扶抑为主、调候只做简化提示,各派取法不同;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
|
|
5012
5651
|
},
|
|
5013
5652
|
];
|
|
5014
5653
|
|
|
5654
|
+
const outline = [
|
|
5655
|
+
{ key: 'overall', label: '整体', text: `${level},喜${join(favor) || '—'}、忌${join(avoid) || '—'}` },
|
|
5656
|
+
{ key: 'career', label: '事业', text: `主格${primary},${topName}偏旺` },
|
|
5657
|
+
{ key: 'wealth', label: '财富', text: `财星权重 ${groups['财'] ?? 0}${favor.includes('金') ? ',喜金助财' : ''}` },
|
|
5658
|
+
{ key: 'love', label: '感情', text: `${spouseKey}星权重 ${groups[spouseKey] ?? 0},日支 ${pick(r.pillars?.day, 'branch', '—')}` },
|
|
5659
|
+
{ key: 'rhythm', label: '节奏', text: `当前${pick(cur, 'ganzhi', '—')}(${curTier}),${pick(ln, 'year', '')} ${pick(ln, 'ganzhi', '')}(${lnTier})` },
|
|
5660
|
+
];
|
|
5661
|
+
const tables = [
|
|
5662
|
+
{
|
|
5663
|
+
key: 'dayun', title: '大运表', columns: ['大运', '年龄', '十神', '档位', '理由'],
|
|
5664
|
+
rows: list.map((d) => [d.ganzhi, `${d.startAge}–${d.startAge + 9}`, d.stemTenGod || '', d.luck?.tier || '平', (d.luck?.reasons || []).join(',')]),
|
|
5665
|
+
},
|
|
5666
|
+
{
|
|
5667
|
+
key: 'liunian', title: '流年表(近十年)', columns: ['年份', '干支', '十神', '档位', '与原局'],
|
|
5668
|
+
rows: (r.liunian || []).slice(0, 10).map((l) => [String(l.year), l.ganzhi, l.stemTenGod || '', l.luck?.tier || '平', (l.relations || []).join('、')]),
|
|
5669
|
+
},
|
|
5670
|
+
];
|
|
5671
|
+
const triggerRows = (r.liunian || [])
|
|
5672
|
+
.filter((l) => Array.isArray(l.relations) && l.relations.length)
|
|
5673
|
+
.slice(0, 10)
|
|
5674
|
+
.map((l) => [String(l.year), l.relations.join('、'), `流年${l.ganzhi}与原局地支:${l.relations.join('、')}`]);
|
|
5675
|
+
if (triggerRows.length) {
|
|
5676
|
+
tables.push({ key: 'triggers', title: '触发事件表', columns: ['年份', '触发', '依据'], rows: triggerRows });
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5015
5679
|
return {
|
|
5016
5680
|
lead: `下面按五个维度拆开说。所有内容来自你这张命盘的结构,讲的是倾向与建议,不是预言——方向供你参考,怎么走在你自己。`,
|
|
5017
|
-
dimensions, sections,
|
|
5681
|
+
dimensions, sections, outline, tables,
|
|
5018
5682
|
};
|
|
5019
5683
|
}
|
|
5020
5684
|
|
|
@@ -5202,6 +5866,10 @@ function ziweiInterpret(r) {
|
|
|
5202
5866
|
return `大限:${dx || '—'}。\n近五年流年:${yrs || '—'}。\n读法:大限定十年的主题,流年四化指出今年往哪边偏;两层方向相反时,以守住大限主题为主。`;
|
|
5203
5867
|
})(),
|
|
5204
5868
|
},
|
|
5869
|
+
{
|
|
5870
|
+
key: 'quant', label: '量化口径',
|
|
5871
|
+
text: '紫微这边只数「宫位里有什么」:大限看落宫与四化命中数、流年看命宫与四化星数;不把星曜折算成分数,也不预测金额与疾病诊断。四化命中数只是结构计数,不等于吉凶强度。',
|
|
5872
|
+
},
|
|
5205
5873
|
{
|
|
5206
5874
|
key: 'limits', label: '未纳入',
|
|
5207
5875
|
text: '本盘没有计算:飞星四化(宫干引发的连锁)、小限与大限的加权、流日流时的具体事项定位,以及双星组合的强弱细分。四化表取中州派常用口径,其他流派(如飞星派)结论会不同;这些不改变盘面事实,只影响轻重,所以只作方向参考。',
|
|
@@ -5237,9 +5905,54 @@ function ziweiInterpret(r) {
|
|
|
5237
5905
|
});
|
|
5238
5906
|
}
|
|
5239
5907
|
|
|
5908
|
+
const outline = [
|
|
5909
|
+
{ key: 'overall', label: '整体', text: `命宫${soulStar}${soulOwn ? '' : '(借对宫)'}${S.trait ? `,${S.trait}` : ''}` },
|
|
5910
|
+
{ key: 'career', label: '事业', text: `官禄${careerStar || '无正曜'}` },
|
|
5911
|
+
{ key: 'wealth', label: '财富', text: `财帛${wealthStar || '无正曜'}` },
|
|
5912
|
+
{ key: 'love', label: '感情', text: `夫妻${spouseStar || '无正曜'}` },
|
|
5913
|
+
{
|
|
5914
|
+
key: 'rhythm', label: '节奏',
|
|
5915
|
+
text: daxianPalace && daxianPalace.daxian
|
|
5916
|
+
? `当前大限${daxianPalace.palace}(${daxianPalace.daxian.start}–${daxianPalace.daxian.end}岁)${r.liunian?.[0]?.year ? `,${r.liunian[0].year} 流年` : ''}`
|
|
5917
|
+
: '当前大限未取到',
|
|
5918
|
+
},
|
|
5919
|
+
];
|
|
5920
|
+
const daxianSorted = (r.daxianList || []).slice().sort((a, b) => a.ages[0] - b.ages[0]);
|
|
5921
|
+
const tables = [
|
|
5922
|
+
{
|
|
5923
|
+
key: 'daxian', title: '大限表', columns: ['大限', '年龄', '会主星', '四化'],
|
|
5924
|
+
rows: daxianSorted.map((d) => [
|
|
5925
|
+
d.palace,
|
|
5926
|
+
`${d.ages[0]}–${d.ages[1]}`,
|
|
5927
|
+
(d.mainStars || []).join('、'),
|
|
5928
|
+
(d.mutagenHits || []).map((m) => `${m.star}${m.type}→${m.palace}`).join('、'),
|
|
5929
|
+
]),
|
|
5930
|
+
},
|
|
5931
|
+
{
|
|
5932
|
+
key: 'liunian', title: '流年表(近六年)', columns: ['年份', '干支', '年龄', '命宫', '四化'],
|
|
5933
|
+
rows: (r.liunian || []).slice(0, 6).map((l) => [
|
|
5934
|
+
String(l.year),
|
|
5935
|
+
l.ganzhi,
|
|
5936
|
+
String(l.age),
|
|
5937
|
+
l.lifePalace ? l.lifePalace.palace : '—',
|
|
5938
|
+
(l.mutagen || []).join('、'),
|
|
5939
|
+
]),
|
|
5940
|
+
},
|
|
5941
|
+
];
|
|
5942
|
+
const triggerRows = daxianSorted
|
|
5943
|
+
.flatMap((d) => (d.mutagenHits || []).map((m) => [
|
|
5944
|
+
`${d.palace}(${d.ages[0]}–${d.ages[1]}岁)`,
|
|
5945
|
+
`${m.star}化${m.type}→${m.palace}`,
|
|
5946
|
+
`大限${d.palace}引动`,
|
|
5947
|
+
]))
|
|
5948
|
+
.slice(0, 12);
|
|
5949
|
+
if (triggerRows.length) {
|
|
5950
|
+
tables.push({ key: 'triggers', title: '触发事件表(大限四化)', columns: ['大限', '触发', '依据'], rows: triggerRows });
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5240
5953
|
return {
|
|
5241
5954
|
lead: `紫微斗数看的是你与环境的互动方式。下面按五个维度拆开,讲的是性格与处境的倾向,具体怎么选仍然在你。`,
|
|
5242
|
-
dimensions, sections,
|
|
5955
|
+
dimensions, sections, outline, tables,
|
|
5243
5956
|
};
|
|
5244
5957
|
}
|
|
5245
5958
|
|
|
@@ -5717,10 +6430,99 @@ const BUILDERS = {
|
|
|
5717
6430
|
};
|
|
5718
6431
|
|
|
5719
6432
|
const { interpretQimen } = __mods['qimen-interpret'];
|
|
6433
|
+
const { buildAnswer, buildPlain, buildGlossary, buildFollowups, dimensionPlain } = __mods['interpret-plain'];
|
|
6434
|
+
const { knowledgeFor, caveatsFor, STATUS_LABEL } = __mods['interpret-knowledge'];
|
|
6435
|
+
|
|
6436
|
+
function enrichPlain(result, r, ask) {
|
|
6437
|
+
if (!result) return result;
|
|
6438
|
+
const dims = (result.dimensions || []).map((d) => {
|
|
6439
|
+
const p = dimensionPlain(d.key, r, {});
|
|
6440
|
+
const know = knowledgeFor(r.system, d.key).map((k) => ({
|
|
6441
|
+
id: k.id,
|
|
6442
|
+
gist: k.gist,
|
|
6443
|
+
detail: k.detail,
|
|
6444
|
+
source: k.source,
|
|
6445
|
+
status: k.status,
|
|
6446
|
+
statusLabel: STATUS_LABEL[k.status] || k.status,
|
|
6447
|
+
note: k.note || '',
|
|
6448
|
+
}));
|
|
6449
|
+
return { ...d, ...(p ? { plain: p } : {}), ...(know.length ? { knowledge: know } : {}) };
|
|
6450
|
+
});
|
|
6451
|
+
const texts = [
|
|
6452
|
+
result.lead,
|
|
6453
|
+
...dims.map((d) => [d.label, d.text, d.advice, d.plain, ...(d.knowledge || []).map((k) => k.detail)].filter(Boolean).join('')),
|
|
6454
|
+
...(result.sections || []).map((x) => x.text),
|
|
6455
|
+
].filter(Boolean);
|
|
6456
|
+
|
|
6457
|
+
const caveats = caveatsFor(r.system);
|
|
6458
|
+
const caveatLine = caveats.length
|
|
6459
|
+
? caveats.slice(0, 4).map((k) => `· ${k.gist}(${STATUS_LABEL[k.status] || k.status})${k.note ? `——${k.note}` : ''}`).join('\n')
|
|
6460
|
+
: '';
|
|
6461
|
+
const secs = [...(result.sections || [])];
|
|
6462
|
+
if (caveatLine) {
|
|
6463
|
+
secs.push({
|
|
6464
|
+
key: 'caveats',
|
|
6465
|
+
label: '这套规则有多稳',
|
|
6466
|
+
text: `下列几条各家说法不一,本插件取其中一派并把分歧标出来,供你判断可信度:\n${caveatLine}`,
|
|
6467
|
+
});
|
|
6468
|
+
}
|
|
6469
|
+
const todos = caveats.filter((k) => k.status === 'todo');
|
|
6470
|
+
if (todos.length) {
|
|
6471
|
+
secs.push({
|
|
6472
|
+
key: 'todo',
|
|
6473
|
+
label: '尚未实现(不猜)',
|
|
6474
|
+
text: todos.map((k) => `· ${k.gist}${k.note ? `——${k.note}` : ''}`).join('\n'),
|
|
6475
|
+
});
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
return {
|
|
6479
|
+
...result,
|
|
6480
|
+
dimensions: dims,
|
|
6481
|
+
sections: secs,
|
|
6482
|
+
answer: result.answer || buildAnswer(r, ask, {}),
|
|
6483
|
+
plain: result.plain || buildPlain(r, ask, {}),
|
|
6484
|
+
followups: buildFollowups(r, ask),
|
|
6485
|
+
glossary: buildGlossary(texts, r.system),
|
|
6486
|
+
knowledgeCount: dims.reduce((n, d) => n + ((d.knowledge || []).length), 0),
|
|
6487
|
+
caveats: caveats.map((k) => ({ id: k.id, gist: k.gist, status: k.status, statusLabel: STATUS_LABEL[k.status] || k.status })),
|
|
6488
|
+
};
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6491
|
+
const TECHNIQUE = {
|
|
6492
|
+
bazi: '八字(月令 · 十神 · 大运流年)',
|
|
6493
|
+
ziwei: '紫微(宫位 · 四化 · 大限流年)',
|
|
6494
|
+
liuyao: '六爻(卦宫 · 世应 · 动爻 · 日辰)',
|
|
6495
|
+
meihua: '梅花(体用 · 互卦 · 变卦)',
|
|
6496
|
+
daliuren: '六壬(月将加时 · 四课 · 三传)',
|
|
6497
|
+
qimen: '奇门(九宫 · 值符值使 · 用神)',
|
|
6498
|
+
tarot: '塔罗(牌阵 · 正逆位 · 元素)',
|
|
6499
|
+
hepan: '合盘(日主 · 夫妻宫 · 五行互补)',
|
|
6500
|
+
};
|
|
6501
|
+
|
|
6502
|
+
const confidenceOf = (dim) => {
|
|
6503
|
+
const n = Array.isArray(dim && dim.evidence) ? dim.evidence.length : 0;
|
|
6504
|
+
return n >= 3 ? '强' : n >= 2 ? '中' : '弱';
|
|
6505
|
+
};
|
|
6506
|
+
|
|
6507
|
+
const withReportMeta = (body, r, ask) => enrichPlain({
|
|
6508
|
+
...body,
|
|
6509
|
+
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
6510
|
+
system: r.system,
|
|
6511
|
+
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
6512
|
+
title: '解析与详解',
|
|
6513
|
+
dimensions: (body.dimensions || []).map((d) => ({ ...d, confidence: d.confidence || confidenceOf(d) })),
|
|
6514
|
+
sections: body.sections || [],
|
|
6515
|
+
outline: body.outline || [],
|
|
6516
|
+
tables: body.tables || [],
|
|
6517
|
+
technique: TECHNIQUE[r.system] || SYSTEM_LABEL[r.system] || r.system,
|
|
6518
|
+
disclaimer: body.disclaimer || DISCLAIMER,
|
|
6519
|
+
ask,
|
|
6520
|
+
}, r, ask);
|
|
5720
6521
|
|
|
5721
6522
|
function interpret(r, opts) {
|
|
5722
6523
|
if (!isObj(r) || !r.system) return null;
|
|
5723
|
-
|
|
6524
|
+
const ask = askOf(opts);
|
|
6525
|
+
if (r.system === 'qimen') return withReportMeta(interpretQimen(r, opts), r, ask);
|
|
5724
6526
|
const fn = BUILDERS[r.system];
|
|
5725
6527
|
if (!fn) return null;
|
|
5726
6528
|
let body;
|
|
@@ -5735,28 +6537,312 @@ function interpret(r, opts) {
|
|
|
5735
6537
|
lead: '这一盘的解析暂未生成,请稍后重试。',
|
|
5736
6538
|
dimensions: [],
|
|
5737
6539
|
sections: [],
|
|
6540
|
+
outline: [],
|
|
6541
|
+
tables: [],
|
|
6542
|
+
technique: TECHNIQUE[r.system] || '',
|
|
5738
6543
|
disclaimer: DISCLAIMER,
|
|
5739
6544
|
error: String(e && e.message),
|
|
5740
|
-
ask
|
|
6545
|
+
ask,
|
|
5741
6546
|
};
|
|
5742
6547
|
}
|
|
5743
|
-
const tuned = applyAsk(body.dimensions || [],
|
|
5744
|
-
return {
|
|
5745
|
-
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
5746
|
-
system: r.system,
|
|
5747
|
-
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
5748
|
-
title: '解析与详解',
|
|
6548
|
+
const tuned = applyAsk(body.dimensions || [], ask, body.lead || '');
|
|
6549
|
+
return withReportMeta({
|
|
5749
6550
|
lead: tuned.lead,
|
|
5750
6551
|
dimensions: tuned.dims,
|
|
5751
6552
|
sections: body.sections || [],
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
};
|
|
6553
|
+
outline: body.outline || [],
|
|
6554
|
+
tables: body.tables || [],
|
|
6555
|
+
}, r, ask);
|
|
5755
6556
|
}
|
|
5756
6557
|
|
|
5757
6558
|
return { DIMENSIONS, MODULE_TYPE, SYSTEM_LABEL, DIRECTIONS, DIRECTION_DEFAULT, dirOf, hexInfo, interpret };
|
|
5758
6559
|
})();
|
|
5759
6560
|
|
|
6561
|
+
__mods.followup = (function () {
|
|
6562
|
+
const { ZIWEI_STARS } = __mods['interpret-data'];
|
|
6563
|
+
|
|
6564
|
+
const TOPICS = [
|
|
6565
|
+
{ key: 'career', label: '事业', words: ['事业', '工作', '跳槽', '升职', '创业', '职业', '岗位', '离职', '加薪', '面试', 'offer'] },
|
|
6566
|
+
{ key: 'wealth', label: '财富', words: ['财', '钱', '收入', '投资', '买房', '存款', '亏', '赚', '分红'] },
|
|
6567
|
+
{ key: 'love', label: '感情', words: ['感情', '婚', '恋', '对象', '分手', '复合', '姻缘', '结婚', '桃花'] },
|
|
6568
|
+
{ key: 'health', label: '健康', words: ['健康', '病', '身体', '手术', '失眠', '体检', '医院'] },
|
|
6569
|
+
{ key: 'study', label: '学业', words: ['学业', '考试', '学习', '考研', '考公', '证书', '升学'] },
|
|
6570
|
+
{ key: 'travel', label: '出行', words: ['出行', '搬家', '旅行', '出国', '迁', '远行'] },
|
|
6571
|
+
];
|
|
6572
|
+
|
|
6573
|
+
const ZW_PALACE_BY_TOPIC = { career: '官禄', wealth: '财帛', love: '夫妻', health: '疾厄', study: '官禄', travel: '迁移', overall: '命宫' };
|
|
6574
|
+
|
|
6575
|
+
const parseYear = (q, r) => {
|
|
6576
|
+
const m = String(q).match(/(20\d{2})\s*年?/);
|
|
6577
|
+
if (m) return Number(m[1]);
|
|
6578
|
+
const base = Array.isArray(r.liunian) && r.liunian[0] ? Number(r.liunian[0].year) : null;
|
|
6579
|
+
if (!base) return null;
|
|
6580
|
+
if (/明年/.test(q)) return base + 1;
|
|
6581
|
+
if (/后年/.test(q)) return base + 2;
|
|
6582
|
+
if (/去年/.test(q)) return base - 1;
|
|
6583
|
+
if (/今年|本年/.test(q)) return base;
|
|
6584
|
+
return null;
|
|
6585
|
+
};
|
|
6586
|
+
|
|
6587
|
+
const topicOf = (q) => TOPICS.find((t) => t.words.some((w) => String(q).includes(w))) || null;
|
|
6588
|
+
|
|
6589
|
+
function baziAnswer(r, q) {
|
|
6590
|
+
const year = parseYear(q, r);
|
|
6591
|
+
const topic = topicOf(q);
|
|
6592
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
6593
|
+
const dayun = Array.isArray(r.dayun?.list) ? r.dayun.list : [];
|
|
6594
|
+
const years = liunian.map((l) => l.year);
|
|
6595
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
6596
|
+
return {
|
|
6597
|
+
ok: false, year, topic,
|
|
6598
|
+
answer: `这一盘已排的流年范围是 ${years[0]}–${years[years.length - 1]},没有 ${year} 年。可以把问题改到范围内,或用 asOf 指定你想看的年份重新排盘。`,
|
|
6599
|
+
evidence: [`已排流年:${years.join('、')}`], confidence: '弱',
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
if (year) {
|
|
6603
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
6604
|
+
const d = dayun.find((x) => Number(x.startAge) <= Number(l.age) && Number(l.age) <= Number(x.startAge) + 9) || null;
|
|
6605
|
+
const tier = l.luck?.tier || '平';
|
|
6606
|
+
const reasons = (l.luck?.reasons || []).slice(0, 3);
|
|
6607
|
+
const rel = (l.relations || []).join('、');
|
|
6608
|
+
const topicLine = topic ? topicSentence(r, topic.key) : '';
|
|
6609
|
+
return {
|
|
6610
|
+
ok: true, year, topic,
|
|
6611
|
+
answer: `${year} 年(${l.ganzhi})流年,十神为${l.stemTenGod || '—'},档位「${tier}」;所行大运 ${d ? `${d.ganzhi}(${d.luck?.tier || '平'})` : '未取到'}。` +
|
|
6612
|
+
`${reasons.length ? `理由:${reasons.join(',')}。` : ''}${rel ? `与该年地支相关的原局关系:${rel}。` : ''}${topicLine}`,
|
|
6613
|
+
evidence: [
|
|
6614
|
+
`流年 ${l.year} ${l.ganzhi}·${l.stemTenGod || ''}·${tier}`,
|
|
6615
|
+
d ? `大运 ${d.ganzhi}·${d.luck?.tier || '平'}` : '',
|
|
6616
|
+
rel ? `原局关系 ${rel}` : '',
|
|
6617
|
+
topic ? `问题方向:${topic.label}` : '',
|
|
6618
|
+
].filter(Boolean),
|
|
6619
|
+
confidence: d && reasons.length ? '强' : '中',
|
|
6620
|
+
};
|
|
6621
|
+
}
|
|
6622
|
+
if (topic) {
|
|
6623
|
+
const near = { wealth: 'overall', study: 'work', travel: 'overall' }[topic.key] || topic.key;
|
|
6624
|
+
const dim = (r.interpret?.dimensions || []).find((x) => x.key === topic.key || x.key === near);
|
|
6625
|
+
if (dim) {
|
|
6626
|
+
const groups = (r.frame && r.frame.godStats && r.frame.godStats.groups) || {};
|
|
6627
|
+
const quant = topic.key === 'wealth'
|
|
6628
|
+
? ` 财星权重 ${Number(groups['财'] || 0)}(只作结构参考,不含金额)。`
|
|
6629
|
+
: '';
|
|
6630
|
+
return {
|
|
6631
|
+
ok: true, year: null, topic,
|
|
6632
|
+
answer: `按${topic.label}这条线看:${dim.text}${quant}${dim.advice ? ` 建议:${dim.advice}` : ''}`,
|
|
6633
|
+
evidence: (dim.evidence || []).slice(0, 4), confidence: dim.confidence || '中',
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
return {
|
|
6638
|
+
ok: false, year: null, topic,
|
|
6639
|
+
answer: `这个问题里没有识别到具体的年份或方面。可以这样问:「2027 年事业怎么样」「财运如何」「感情在 2028 年」;已排流年是 ${years[0]}–${years[years.length - 1]}。`,
|
|
6640
|
+
evidence: [`可问年份:${years.join('、')}`], confidence: '弱',
|
|
6641
|
+
};
|
|
6642
|
+
}
|
|
6643
|
+
|
|
6644
|
+
function ziweiAnswer(r, q) {
|
|
6645
|
+
const year = parseYear(q, r);
|
|
6646
|
+
const topic = topicOf(q);
|
|
6647
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
6648
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
6649
|
+
return {
|
|
6650
|
+
ok: false, year, topic,
|
|
6651
|
+
answer: `这一盘的流年表覆盖 ${liunian[0]?.year}–${liunian[liunian.length - 1]?.year},没有 ${year} 年;可以换一个年份,或用 asOf 指定起点重新排盘。`,
|
|
6652
|
+
evidence: [`流年表:${liunian.map((l) => l.year).join('、')}`], confidence: '弱',
|
|
6653
|
+
};
|
|
6654
|
+
}
|
|
6655
|
+
if (year) {
|
|
6656
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
6657
|
+
const dx = l.daxian || {};
|
|
6658
|
+
return {
|
|
6659
|
+
ok: true, year, topic,
|
|
6660
|
+
answer: `${year} 年(${l.ganzhi},${l.age} 岁)流年命宫落在「${l.lifePalace ? l.lifePalace.palace : '—'}」,四化为 ${(l.mutagen || []).join('、') || '不明显'};所行大限${dx.palace ? `在${dx.palace}(${dx.start}–${dx.end} 岁)` : '未取到'}。${topic ? `与「${topic.label}」对应的宫位信号见下条依据。` : ''}`,
|
|
6661
|
+
evidence: [
|
|
6662
|
+
`流年命宫 ${l.lifePalace ? l.lifePalace.palace : '—'}(${l.lifePalace ? l.lifePalace.branch : '—'})`,
|
|
6663
|
+
`四化 ${(l.mutagen || []).join('、') || '不明显'}`,
|
|
6664
|
+
dx.palace ? `大限 ${dx.palace}·${dx.start}–${dx.end}岁` : '',
|
|
6665
|
+
].filter(Boolean),
|
|
6666
|
+
confidence: (l.mutagen || []).length >= 3 ? '强' : '中',
|
|
6667
|
+
};
|
|
6668
|
+
}
|
|
6669
|
+
if (topic) {
|
|
6670
|
+
const name = ZW_PALACE_BY_TOPIC[topic.key] || '命宫';
|
|
6671
|
+
const p = (r.palaces || []).find((x) => x.palace === name);
|
|
6672
|
+
if (p) {
|
|
6673
|
+
const stars = (p.stars || []).filter((s) => s.type === '主星').map((s) => s.name);
|
|
6674
|
+
const trait = stars.map((s) => ZIWEI_STARS[s]?.trait).filter(Boolean).slice(0, 2).join(';');
|
|
6675
|
+
return {
|
|
6676
|
+
ok: true, year: null, topic,
|
|
6677
|
+
answer: `「${topic.label}」看${name}:主星${stars.length ? stars.join('、') : '无正曜(借对宫)'}${trait ? `——${trait}` : ''}。宫支${p.branch},${p.stems ? '' : ''}生年四化与煞星的影响需结合报告里的对应分节一起看。`,
|
|
6678
|
+
evidence: [`${name} ${p.branch}·${stars.join('、') || '无正曜'}`, ...(p.shensha || []).slice(0, 3)],
|
|
6679
|
+
confidence: stars.length ? '强' : '弱',
|
|
6680
|
+
};
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
return {
|
|
6684
|
+
ok: false, year: null, topic,
|
|
6685
|
+
answer: '没有识别到具体年份或方面。可以问「2028 年事业」「感情怎么样」「财运在 2030 年」;也可以直接问某个宫位,如「夫妻宫」。',
|
|
6686
|
+
evidence: [], confidence: '弱',
|
|
6687
|
+
};
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
function topicSentence(r, key) {
|
|
6691
|
+
const map = { career: '事业', wealth: '财富', love: '感情', health: '健康', study: '学业', travel: '出行' };
|
|
6692
|
+
return `这段时间可以重点对照报告里「${map[key] || '整体'}」一节看。`;
|
|
6693
|
+
}
|
|
6694
|
+
|
|
6695
|
+
function genericAnswer(r, q) {
|
|
6696
|
+
const topic = topicOf(q);
|
|
6697
|
+
const sections = Array.isArray(r.interpret?.sections) ? r.interpret.sections : [];
|
|
6698
|
+
const hit = topic
|
|
6699
|
+
? sections.find((s) => s.label.includes(topic.label) || s.text.includes(topic.label))
|
|
6700
|
+
: null;
|
|
6701
|
+
if (hit) {
|
|
6702
|
+
return { ok: true, year: null, topic, answer: hit.text, evidence: [`引用分节:${hit.label}`], confidence: '中' };
|
|
6703
|
+
}
|
|
6704
|
+
const first = sections[0];
|
|
6705
|
+
return {
|
|
6706
|
+
ok: false, year: null, topic,
|
|
6707
|
+
answer: first
|
|
6708
|
+
? `这一盘的追问需要落到具体方面(事业 / 财富 / 感情 / 健康 / 学业 / 出行)。先看「${first.label}」这一节,或把问题说得更具体一点。`
|
|
6709
|
+
: '这一盘暂时没有可追问的分节;先看完整解析,再问具体方面。',
|
|
6710
|
+
evidence: [], confidence: '弱',
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
|
|
6714
|
+
function followup(r, question) {
|
|
6715
|
+
const q = String(question || '').trim();
|
|
6716
|
+
const result = r && r.system === 'bazi' ? baziAnswer(r, q)
|
|
6717
|
+
: r && r.system === 'ziwei' ? ziweiAnswer(r, q)
|
|
6718
|
+
: genericAnswer(r, q);
|
|
6719
|
+
return { question: q, ...result };
|
|
6720
|
+
}
|
|
6721
|
+
|
|
6722
|
+
return { followup };
|
|
6723
|
+
})();
|
|
6724
|
+
|
|
6725
|
+
__mods.crosscheck = (function () {
|
|
6726
|
+
const { ZIWEI_STARS } = __mods['interpret-data'];
|
|
6727
|
+
|
|
6728
|
+
const groupsOf = (bazi) => (bazi && bazi.frame && bazi.frame.godStats && bazi.frame.godStats.groups) || {};
|
|
6729
|
+
const topOf = (groups) => Object.entries(groups).sort((a, b) => Number(b[1]) - Number(a[1]))[0] || null;
|
|
6730
|
+
const zwPalace = (zw, name) => (zw.palaces || []).find((p) => p.palace === name) || null;
|
|
6731
|
+
const zwMain = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '主星').map((s) => s.name) : []);
|
|
6732
|
+
const countSha = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '煞星').length : 0);
|
|
6733
|
+
const tierOf = (bazi) => {
|
|
6734
|
+
const age = Number(bazi.liunian && bazi.liunian[0] ? bazi.liunian[0].age : NaN);
|
|
6735
|
+
const list = (bazi.dayun && bazi.dayun.list) || [];
|
|
6736
|
+
let cur = null;
|
|
6737
|
+
for (const d of list) if (Number.isFinite(age) && Number(d.startAge) <= age) cur = d;
|
|
6738
|
+
return cur ? (cur.luck && cur.luck.tier) || '平' : '—';
|
|
6739
|
+
};
|
|
6740
|
+
const daxianOf = (zw) => {
|
|
6741
|
+
const age = Number(zw.liunian && zw.liunian[0] ? zw.liunian[0].age : NaN);
|
|
6742
|
+
for (const p of zw.palaces || []) {
|
|
6743
|
+
const d = p.daxian || {};
|
|
6744
|
+
if (Number.isFinite(age) && Number(d.start) <= age && age <= Number(d.end)) return p;
|
|
6745
|
+
}
|
|
6746
|
+
return null;
|
|
6747
|
+
};
|
|
6748
|
+
|
|
6749
|
+
function crossCheck(bazi, zw) {
|
|
6750
|
+
const groups = groupsOf(bazi);
|
|
6751
|
+
const top = topOf(groups);
|
|
6752
|
+
const items = [];
|
|
6753
|
+
const push = (key, label, baziText, ziweiText, stance, note) => items.push({ key, label, bazi: baziText, ziwei: ziweiText, stance, note });
|
|
6754
|
+
|
|
6755
|
+
{
|
|
6756
|
+
const career = zwPalace(zw, '官禄');
|
|
6757
|
+
const main = zwMain(career);
|
|
6758
|
+
const baziStrong = Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0) >= 2;
|
|
6759
|
+
const ziweiHas = main.length > 0;
|
|
6760
|
+
const stance = baziStrong && ziweiHas ? '同向' : !baziStrong && !ziweiHas ? '同向' : '有张力';
|
|
6761
|
+
push('career', '事业主线',
|
|
6762
|
+
`${top ? `十神以${top[0]}最重(${top[1]})` : '十神未取到'};官杀+食伤权重 ${(Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0)).toFixed(1)}`,
|
|
6763
|
+
main.length ? `官禄宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].work})` : ''}` : '官禄无正曜',
|
|
6764
|
+
stance,
|
|
6765
|
+
stance === '同向' ? '两边都指向(或都不强调)事业主线,方向可互参' : '一边强调事业主线、一边信号弱,建议以信号强的一边为主,另一边作补充');
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
{
|
|
6769
|
+
const wealth = zwPalace(zw, '财帛');
|
|
6770
|
+
const main = zwMain(wealth);
|
|
6771
|
+
const sha = countSha(wealth);
|
|
6772
|
+
const w = Number(groups['财'] || 0);
|
|
6773
|
+
const baziStrong = w >= 2;
|
|
6774
|
+
const ziweiStrong = main.length > 0;
|
|
6775
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
6776
|
+
push('wealth', '财运结构',
|
|
6777
|
+
`财星权重 ${w}${baziStrong ? '(偏强)' : '(偏弱)'}`,
|
|
6778
|
+
`${main.length ? `财帛主星 ${main.join('、')}` : '财帛无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
6779
|
+
stance,
|
|
6780
|
+
'两套体系对「财」的定义不同:八字看十神财星,紫微看财帛宫,只作对照,不作数值合并');
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
{
|
|
6784
|
+
const spouse = zwPalace(zw, '夫妻');
|
|
6785
|
+
const main = zwMain(spouse);
|
|
6786
|
+
const isMale = bazi.input && bazi.input.gender === 'male';
|
|
6787
|
+
const key = isMale ? '财' : '官杀';
|
|
6788
|
+
const v = Number(groups[key] || 0);
|
|
6789
|
+
const baziStrong = v >= 2;
|
|
6790
|
+
const ziweiStrong = main.length > 0;
|
|
6791
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
6792
|
+
push('love', '感情线索',
|
|
6793
|
+
`${isMale ? '妻' : '夫'}星(${key})权重 ${v}${baziStrong ? '(不弱)' : '(偏弱)'}`,
|
|
6794
|
+
main.length ? `夫妻宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].love})` : ''}` : '夫妻宫无正曜',
|
|
6795
|
+
stance,
|
|
6796
|
+
stance === '同向' ? '两边对感情的强弱判断接近' : '一边说缘分不弱、一边主星信号弱,宜按「多主动创造场景」处理');
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6799
|
+
{
|
|
6800
|
+
const wx = bazi.wuxing || {};
|
|
6801
|
+
const weakest = Object.entries(wx).sort((a, b) => Number(a[1]) - Number(b[1]))[0];
|
|
6802
|
+
const health = zwPalace(zw, '疾厄');
|
|
6803
|
+
const main = zwMain(health);
|
|
6804
|
+
const sha = countSha(health);
|
|
6805
|
+
push('health', '身体关注点',
|
|
6806
|
+
weakest ? `最弱五行 ${weakest[0]}(${weakest[1]})` : '五行未取到',
|
|
6807
|
+
`${main.length ? `疾厄主星 ${main.join('、')}` : '疾厄无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
6808
|
+
'互补',
|
|
6809
|
+
'两边指向的器官/系统不一定相同,合并起来看更稳;任何健康问题以体检与医生为准');
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6812
|
+
{
|
|
6813
|
+
const t = tierOf(bazi);
|
|
6814
|
+
const d = daxianOf(zw);
|
|
6815
|
+
const sha = d ? countSha(d) : 0;
|
|
6816
|
+
const good = ['吉', '大吉'];
|
|
6817
|
+
const bad = ['忌', '大忌'];
|
|
6818
|
+
const baziGood = good.includes(t);
|
|
6819
|
+
const baziBad = bad.includes(t);
|
|
6820
|
+
const ziweiHeavy = sha >= 2;
|
|
6821
|
+
let stance = '有张力';
|
|
6822
|
+
let note = '一边顺、一边压,按「大方向跟顺的一边、具体动作避开压的年份」处理';
|
|
6823
|
+
if ((baziGood && !ziweiHeavy) || (baziBad && ziweiHeavy) || (!baziGood && !baziBad && !ziweiHeavy)) {
|
|
6824
|
+
stance = '同向';
|
|
6825
|
+
note = '两边对当前阶段的松紧判断接近';
|
|
6826
|
+
}
|
|
6827
|
+
push('rhythm', '当前节奏',
|
|
6828
|
+
`大运档位 ${t}`,
|
|
6829
|
+
`${d ? `大限在${d.palace}` : '大限未取到'}${sha ? `;煞星 ${sha} 颗` : ';无煞星'}`,
|
|
6830
|
+
stance, note);
|
|
6831
|
+
}
|
|
6832
|
+
|
|
6833
|
+
return { items };
|
|
6834
|
+
}
|
|
6835
|
+
|
|
6836
|
+
function crossText(cross) {
|
|
6837
|
+
if (!cross || !Array.isArray(cross.items)) return '';
|
|
6838
|
+
return cross.items
|
|
6839
|
+
.map((i) => `· ${i.label}:八字——${i.bazi};紫微——${i.ziwei};判断:${i.stance}(${i.note})`)
|
|
6840
|
+
.join('\n');
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6843
|
+
return { crossCheck, crossText };
|
|
6844
|
+
})();
|
|
6845
|
+
|
|
5760
6846
|
__mods.index = (function () {
|
|
5761
6847
|
const { buildBazi } = __mods.bazi;
|
|
5762
6848
|
const { buildLiuyao, tossLines } = __mods.liuyao;
|
|
@@ -5771,15 +6857,13 @@ const { geocodeAddress, resolveAddress } = __mods.geo;
|
|
|
5771
6857
|
const { toJDN, ganzhiName, dayGanzhiIndex } = __mods.calendar;
|
|
5772
6858
|
const { randomOf, isSeed } = __mods.random;
|
|
5773
6859
|
const { interpret, DIRECTIONS, DIRECTION_DEFAULT } = __mods.interpret;
|
|
6860
|
+
const { followup } = __mods.followup;
|
|
6861
|
+
const { crossCheck, crossText } = __mods.crosscheck;
|
|
6862
|
+
const { fail } = __mods.fail;
|
|
6863
|
+
const { assertCivil } = __mods.validate;
|
|
5774
6864
|
|
|
5775
6865
|
const pad = (n) => String(n).padStart(2, '0');
|
|
5776
6866
|
|
|
5777
|
-
const fail = (code, message) => {
|
|
5778
|
-
const e = new Error(message);
|
|
5779
|
-
e.code = code;
|
|
5780
|
-
return e;
|
|
5781
|
-
};
|
|
5782
|
-
|
|
5783
6867
|
const GENDERS = ['male', 'female'];
|
|
5784
6868
|
|
|
5785
6869
|
function daysInMonth(year, month) {
|
|
@@ -6034,19 +7118,59 @@ function compute(args = {}) {
|
|
|
6034
7118
|
direction: typeof args.direction === 'string' ? args.direction : 'general',
|
|
6035
7119
|
});
|
|
6036
7120
|
}
|
|
7121
|
+
if (args.cross === true && (r.system === 'bazi' || r.system === 'ziwei')) {
|
|
7122
|
+
const cdt = parseDateTime(args.datetime);
|
|
7123
|
+
const common = {
|
|
7124
|
+
...cdt,
|
|
7125
|
+
gender: requireGender(args.gender),
|
|
7126
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
7127
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
7128
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
7129
|
+
trueSolar: args.trueSolar !== false,
|
|
7130
|
+
};
|
|
7131
|
+
const asOf = checkAsOf(args.asOf);
|
|
7132
|
+
const other = r.system === 'bazi'
|
|
7133
|
+
? buildZiwei({ ...common, leapAsNext: args.leapAsNext === true, asOf })
|
|
7134
|
+
: buildBazi({ ...common, lateZi: args.lateZi !== false, nowYear: asOf ? asOf.year : undefined });
|
|
7135
|
+
r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
|
|
7136
|
+
if (r.interpret) {
|
|
7137
|
+
r.interpret.sections.push({ key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: crossText(r.cross) });
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
if (typeof args.ask === 'string' && args.ask.trim()) r.followup = followup(r, args.ask);
|
|
6037
7141
|
return r;
|
|
6038
7142
|
}
|
|
6039
7143
|
|
|
6040
7144
|
function interpretText(i) {
|
|
6041
7145
|
if (!i) return '';
|
|
6042
7146
|
const out = [`【${i.title || '解析与详解'}】${i.ask && i.ask.echo ? i.ask.echo : ''}`];
|
|
7147
|
+
if (i.answer) out.push(`▶ 先答所问:${i.answer}`);
|
|
7148
|
+
if (i.plain) out.push(`▶ 说人话:${i.plain}`);
|
|
6043
7149
|
if (i.lead) out.push(i.lead);
|
|
7150
|
+
if (Array.isArray(i.outline) && i.outline.length) {
|
|
7151
|
+
out.push('一页读懂:' + i.outline.map((o) => `${o.label}·${o.text}`).join(' '));
|
|
7152
|
+
}
|
|
6044
7153
|
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
6045
7154
|
for (const d of i.dimensions || []) {
|
|
6046
|
-
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}` : '';
|
|
6047
|
-
|
|
7155
|
+
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
|
|
7156
|
+
const plain = d.plain ? `(${d.plain})` : '';
|
|
7157
|
+
const know = (d.knowledge || [])
|
|
7158
|
+
.map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
|
|
7159
|
+
.join('');
|
|
7160
|
+
out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
|
|
6048
7161
|
}
|
|
6049
7162
|
for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
|
|
7163
|
+
for (const t of i.tables || []) {
|
|
7164
|
+
if (!Array.isArray(t.rows) || !t.rows.length) continue;
|
|
7165
|
+
out.push(`【${t.title}】`);
|
|
7166
|
+
out.push([t.columns.join(' | '), ...t.rows.map((r) => r.join(' | '))].join('\n'));
|
|
7167
|
+
}
|
|
7168
|
+
if (Array.isArray(i.followups) && i.followups.length) {
|
|
7169
|
+
out.push('还可以这样追问:' + i.followups.map((q) => `「${q}」`).join(' '));
|
|
7170
|
+
}
|
|
7171
|
+
if (Array.isArray(i.glossary) && i.glossary.length) {
|
|
7172
|
+
out.push('名词解释:' + i.glossary.map((g) => `${g.term}=${g.plain}`).join(' '));
|
|
7173
|
+
}
|
|
6050
7174
|
if (i.disclaimer) out.push(i.disclaimer);
|
|
6051
7175
|
return out.join('\n');
|
|
6052
7176
|
}
|
|
@@ -6218,12 +7342,20 @@ function computeRaw(args = {}) {
|
|
|
6218
7342
|
|
|
6219
7343
|
function renderText(result) {
|
|
6220
7344
|
const base = result.summary ?? JSON.stringify(result, null, 2);
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
7345
|
+
const parts = [base];
|
|
7346
|
+
if (result.interpret) {
|
|
7347
|
+
const extra = interpretText(result.interpret);
|
|
7348
|
+
if (extra) parts.push(extra);
|
|
7349
|
+
}
|
|
7350
|
+
if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
|
|
7351
|
+
if (result.followup) {
|
|
7352
|
+
const f = result.followup;
|
|
7353
|
+
parts.push(`【追问】${f.question}\n${f.answer}${f.evidence && f.evidence.length ? `\n 依据:${f.evidence.join(';')} 置信度:${f.confidence}` : ''}`);
|
|
7354
|
+
}
|
|
7355
|
+
return parts.filter(Boolean).join('\n\n');
|
|
6224
7356
|
}
|
|
6225
7357
|
|
|
6226
|
-
return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, parseDateTime, compute, interpretText, renderText };
|
|
7358
|
+
return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, crossCheck, crossText, parseDateTime, compute, interpretText, renderText };
|
|
6227
7359
|
})();
|
|
6228
7360
|
|
|
6229
7361
|
const Engine = __mods.index;
|
|
@@ -6574,6 +7706,17 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
6574
7706
|
.mp-it-focus{margin-left:6px;font-size:12px;color:var(--lu);border:1px solid rgba(var(--lu-rgb),.35);border-radius:999px;padding:1px 9px;font-family:system-ui,sans-serif}
|
|
6575
7707
|
.mp-it-dim.focus{background:linear-gradient(90deg,rgba(var(--lu-rgb),.06),transparent 60%);border-left:2px solid var(--lu);padding-left:10px}
|
|
6576
7708
|
.mp-it-lead{padding:11px 16px;font-size:13px;color:var(--ink2);border-bottom:1px dashed var(--line)}
|
|
7709
|
+
.mp-it-ans{margin:12px 16px 0;padding:12px 14px;border-left:3px solid var(--red);background:var(--band);font-size:13.5px;line-height:1.9;text-align:justify}
|
|
7710
|
+
.mp-it-ans b{display:block;font-size:11px;letter-spacing:.12em;color:var(--red);font-family:system-ui,sans-serif;margin-bottom:5px;font-weight:600}
|
|
7711
|
+
.mp-it-plain{margin:10px 16px 0;padding:11px 14px;border:1px dashed var(--line);border-radius:8px;font-size:13px;line-height:1.9;color:var(--ink2);text-align:justify}
|
|
7712
|
+
.mp-it-plain b{color:var(--lu);font-weight:600;margin-right:6px}
|
|
7713
|
+
.mp-it-dplain{margin-bottom:6px;font-size:12.5px;line-height:1.8;color:var(--lu)}
|
|
7714
|
+
.mp-it-fu{margin:4px 16px 12px;padding:11px 14px;background:var(--band);border-radius:8px}
|
|
7715
|
+
.mp-it-fu b{display:block;font-size:11px;letter-spacing:.12em;color:var(--label);font-family:system-ui,sans-serif;margin-bottom:6px;font-weight:600}
|
|
7716
|
+
.mp-it-fu li{font-size:12.5px;line-height:1.85;margin-left:16px;color:var(--ink2)}
|
|
7717
|
+
.mp-it-gloss{margin:0 16px 12px;font-size:12px;line-height:1.85;color:var(--muted)}
|
|
7718
|
+
.mp-it-gloss summary{cursor:pointer;font-family:system-ui,sans-serif;letter-spacing:.06em;color:var(--label);margin-bottom:6px}
|
|
7719
|
+
.mp-it-gloss span{color:var(--ink2);font-weight:600;margin-right:4px}
|
|
6577
7720
|
.mp-it-err{margin:12px 16px;padding:9px 12px;border-left:2px solid var(--red);background:var(--soul);font-size:12.5px;line-height:1.8;color:var(--ink2)}
|
|
6578
7721
|
.mp-it-err b{color:var(--red);margin-right:6px}
|
|
6579
7722
|
.mp-it-body{padding:2px 16px 12px}
|
|
@@ -6586,11 +7729,62 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
6586
7729
|
.mp-it-dev{margin-top:7px;font-size:12px;line-height:1.75;color:var(--muted)}
|
|
6587
7730
|
.mp-it-dev b{color:var(--label);font-weight:600;margin-right:7px;font-family:system-ui,sans-serif;font-size:11px;letter-spacing:.1em}
|
|
6588
7731
|
.mp-it-adv{margin-top:8px;padding:9px 12px;background:var(--band);border-left:2px solid var(--red);font-size:13px;line-height:1.85}
|
|
7732
|
+
.mp-it-know{margin-top:8px;display:flex;flex-direction:column;gap:6px}
|
|
7733
|
+
.know-row{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.8;color:var(--ink2)}
|
|
7734
|
+
.know-status{flex:none;margin-top:2px;padding:1px 6px;border-radius:4px;font-size:10.5px;letter-spacing:.04em;font-family:system-ui,sans-serif;border:1px solid var(--line);color:var(--muted);white-space:nowrap}
|
|
7735
|
+
.know-status.settled{color:var(--good)}
|
|
7736
|
+
.know-status.variant{color:var(--warn)}
|
|
7737
|
+
.know-status.todo{color:var(--red)}
|
|
7738
|
+
.know-body b{display:block;font-size:12.5px;color:var(--ink);font-weight:600;margin-bottom:2px}
|
|
7739
|
+
.know-body i{display:block;font-style:normal;color:var(--muted);font-size:11.5px;margin-top:2px}
|
|
7740
|
+
.know-body em{display:block;font-style:normal;color:var(--warn);font-size:11.5px;margin-top:2px}
|
|
6589
7741
|
.mp-it-adv b{color:var(--red);font-weight:600;margin-right:7px;font-family:system-ui,sans-serif;font-size:12px;letter-spacing:.1em}
|
|
6590
7742
|
.mp-it-secs{border-top:1px solid var(--line);padding:12px 16px 2px;background:var(--card)}
|
|
6591
7743
|
.mp-it-sec{margin-bottom:11px}
|
|
6592
7744
|
.mp-it-sh{font-size:13px;font-weight:700;color:var(--lu);letter-spacing:.06em;margin-bottom:4px}
|
|
6593
7745
|
.mp-it-stext{font-size:13px;line-height:1.9;color:var(--ink2);text-align:justify}
|
|
7746
|
+
.mp-outline{margin:12px 16px 0;border:1px solid var(--line);border-left:3px solid var(--lu);border-radius:8px;background:var(--card);padding:10px 12px}
|
|
7747
|
+
.mp-outline-h{font-size:12px;font-weight:700;letter-spacing:.12em;color:var(--lu);margin-bottom:6px;font-family:system-ui,sans-serif}
|
|
7748
|
+
.mp-outline-row{display:grid;grid-template-columns:56px minmax(0,1fr);gap:8px;padding:3px 0;font-size:13px;line-height:1.7;color:var(--ink2)}
|
|
7749
|
+
.mp-outline-row b{color:var(--label);font-weight:600;font-family:system-ui,sans-serif}
|
|
7750
|
+
.mp-toc{display:flex;flex-wrap:wrap;gap:6px 10px;margin:10px 16px 0;padding:8px 10px;border:1px dashed var(--line);border-radius:8px;font-size:12px;font-family:system-ui,sans-serif}
|
|
7751
|
+
.mp-toc a{color:var(--lu);text-decoration:none;border-bottom:1px dotted rgba(var(--lu-rgb),.5)}
|
|
7752
|
+
.mp-toc a:hover{color:var(--ink)}
|
|
7753
|
+
.mp-it-ev{margin-top:7px;border:1px solid var(--line);border-radius:6px;background:var(--band);font-size:12px;font-family:system-ui,sans-serif}
|
|
7754
|
+
.mp-it-ev>summary{cursor:pointer;padding:6px 9px;color:var(--muted);list-style:none}
|
|
7755
|
+
.mp-it-ev>summary::-webkit-details-marker{display:none}
|
|
7756
|
+
.mp-it-ev>summary::before{content:'▸ ';color:var(--label)}
|
|
7757
|
+
.mp-it-ev[open]>summary::before{content:'▾ '}
|
|
7758
|
+
.mp-it-ev-body{padding:2px 9px 8px;display:flex;flex-direction:column;gap:4px}
|
|
7759
|
+
.mp-it-ev-item{color:var(--ink2);line-height:1.7;padding-left:12px;position:relative}
|
|
7760
|
+
.mp-it-ev-item::before{content:'·';position:absolute;left:2px;color:var(--label)}
|
|
7761
|
+
.mp-it-ev-tech{margin-top:4px;color:var(--label);font-size:11px;letter-spacing:.06em}
|
|
7762
|
+
.mp-it-conf{font-size:11px;font-family:system-ui,sans-serif;border-radius:999px;padding:1px 8px;margin-left:6px;border:1px solid var(--line);color:var(--muted)}
|
|
7763
|
+
.mp-it-conf.c-强{color:var(--lu);border-color:rgba(var(--lu-rgb),.4)}
|
|
7764
|
+
.mp-it-conf.c-中{color:var(--label);border-color:var(--line)}
|
|
7765
|
+
.mp-it-conf.c-弱{color:var(--red);border-color:rgba(var(--red-rgb),.4)}
|
|
7766
|
+
.mp-tables{border-top:1px solid var(--line);padding:12px 16px 4px;background:var(--card)}
|
|
7767
|
+
.mp-table-wrap{margin-bottom:12px}
|
|
7768
|
+
.mp-table-h{font-size:12px;font-weight:700;letter-spacing:.1em;color:var(--lu);margin-bottom:5px;font-family:system-ui,sans-serif}
|
|
7769
|
+
.mp-table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:6px}
|
|
7770
|
+
.mp-table{width:100%;border-collapse:collapse;font-size:12px;line-height:1.6;color:var(--ink2);font-family:system-ui,sans-serif}
|
|
7771
|
+
.mp-table th,.mp-table td{padding:5px 9px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);white-space:nowrap}
|
|
7772
|
+
.mp-table th{background:var(--band);color:var(--label);font-weight:600;font-size:11px;letter-spacing:.06em}
|
|
7773
|
+
.mp-table tbody tr:last-child td{border-bottom:none}
|
|
7774
|
+
.mp-table tbody tr:nth-child(even) td{background:rgba(0,0,0,.02)}
|
|
7775
|
+
@media print{
|
|
7776
|
+
.mp-toc{display:none}
|
|
7777
|
+
.mp-it.folded .mp-it-wrap{display:block}
|
|
7778
|
+
.mp-table-scroll{overflow:visible}
|
|
7779
|
+
.mp-table th,.mp-table td{white-space:normal}
|
|
7780
|
+
.mp-table-wrap,.mp-it-sec,.mp-it-dim{break-inside:avoid}
|
|
7781
|
+
.mp-table-wrap{page-break-inside:avoid}
|
|
7782
|
+
}
|
|
7783
|
+
.mp-fu{margin:12px 16px;border:1px solid var(--line);border-left:3px solid var(--lu);border-radius:8px;background:var(--card);padding:10px 12px;font-size:13px;line-height:1.8;color:var(--ink2)}
|
|
7784
|
+
.mp-fu.miss{border-left-color:var(--red)}
|
|
7785
|
+
.mp-fu-h{font-size:12px;font-weight:700;letter-spacing:.08em;color:var(--lu);margin-bottom:4px;font-family:system-ui,sans-serif}
|
|
7786
|
+
.mp-fu.miss .mp-fu-h{color:var(--red)}
|
|
7787
|
+
.mp-fu-ev{margin-top:5px;font-size:12px;color:var(--muted);font-family:system-ui,sans-serif}
|
|
6594
7788
|
.mp-it-dis{padding:11px 16px;border-top:1px solid var(--line);background:var(--band);font-size:12px;line-height:1.75;color:var(--muted);font-family:system-ui,sans-serif}
|
|
6595
7789
|
|
|
6596
7790
|
@media (max-width:640px){
|
|
@@ -7437,16 +8631,34 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
7437
8631
|
|
|
7438
8632
|
pp.interpretHTML = function (it, opt) {
|
|
7439
8633
|
if (!it) return '';
|
|
7440
|
-
|
|
8634
|
+
const hasBody = (it.dimensions && it.dimensions.length) || (it.sections && it.sections.length);
|
|
8635
|
+
if (!hasBody && !it.error && !it.answer) return '';
|
|
7441
8636
|
const folded = !!(opt && opt.folded);
|
|
7442
|
-
const dims = it.dimensions.map((d, i) => `<div class="mp-it-dim${d.focused ? ' focus' : ''}">
|
|
7443
|
-
<div class="mp-it-dh"><span class="mp-it-dno">${i + 1}</span><span class="mp-it-dlabel">${esc(d.label)}</span>${d.focused ? '<span class="mp-it-focus">最贴近你的问题</span>' : ''}</div>
|
|
8637
|
+
const dims = (it.dimensions || []).map((d, i) => `<div class="mp-it-dim${d.focused ? ' focus' : ''}">
|
|
8638
|
+
<div class="mp-it-dh"><span class="mp-it-dno">${i + 1}</span><span class="mp-it-dlabel">${esc(d.label)}</span>${d.focused ? '<span class="mp-it-focus">最贴近你的问题</span>' : ''}${d.confidence ? `<span class="mp-it-conf c-${esc(d.confidence)}">置信度 ${esc(d.confidence)}</span>` : ''}</div>
|
|
8639
|
+
${d.plain ? `<div class="mp-it-dplain">${esc(d.plain)}</div>` : ''}
|
|
7444
8640
|
<div class="mp-it-dtext">${esc(d.text)}</div>
|
|
7445
|
-
${(d.evidence && d.evidence.length) ? `<div class="mp-it-
|
|
8641
|
+
${(d.evidence && d.evidence.length) ? `<details class="mp-it-ev"><summary>依据 · ${esc(d.evidence.length)} 条盘面事实</summary><div class="mp-it-ev-body">${d.evidence.map((x) => `<span class="mp-it-ev-item">${esc(x)}</span>`).join('')}<span class="mp-it-ev-tech">技法:${esc(it.technique || it.systemLabel || '')}</span></div></details>` : ''}
|
|
8642
|
+
${(d.knowledge && d.knowledge.length) ? `<div class="mp-it-know">${d.knowledge.map((k) => `<div class="know-row"><span class="know-status ${k.status}">${esc(k.statusLabel)}</span><span class="know-body"><b>${esc(k.gist)}</b>${esc(k.detail)}${k.source ? `<i>出处:${esc(k.source)}</i>` : ''}${k.note ? `<em>注:${esc(k.note)}</em>` : ''}</span></div>`).join('')}</div>` : ''}
|
|
7446
8643
|
${d.advice ? `<div class="mp-it-adv"><b>建议</b>${esc(d.advice)}</div>` : ''}
|
|
7447
8644
|
</div>`).join('');
|
|
8645
|
+
const outline = (it.outline && it.outline.length)
|
|
8646
|
+
? `<div class="mp-outline"><div class="mp-outline-h">一页读懂</div>${it.outline.map((o) => `<div class="mp-outline-row"><b>${esc(o.label)}</b><span>${esc(o.text)}</span></div>`).join('')}</div>`
|
|
8647
|
+
: '';
|
|
8648
|
+
const toc = (it.sections && it.sections.length)
|
|
8649
|
+
? `<nav class="mp-toc" aria-label="报告目录">${it.sections.map((s) => `<a href="#mp-sec-${esc(s.key)}">${esc(s.label)}</a>`).join('')}</nav>`
|
|
8650
|
+
: '';
|
|
8651
|
+
const tables = (it.tables && it.tables.length)
|
|
8652
|
+
? `<div class="mp-tables">${it.tables.filter((t) => Array.isArray(t.rows) && t.rows.length).map((t) => `<div class="mp-table-wrap">
|
|
8653
|
+
<div class="mp-table-h">${esc(t.title)}</div>
|
|
8654
|
+
<div class="mp-table-scroll"><table class="mp-table">
|
|
8655
|
+
<thead><tr>${t.columns.map((c) => `<th>${esc(c)}</th>`).join('')}</tr></thead>
|
|
8656
|
+
<tbody>${t.rows.map((row) => `<tr>${row.map((c) => `<td>${esc(c)}</td>`).join('')}</tr>`).join('')}</tbody>
|
|
8657
|
+
</table></div>
|
|
8658
|
+
</div>`).join('')}</div>`
|
|
8659
|
+
: '';
|
|
7448
8660
|
const secs = (it.sections && it.sections.length)
|
|
7449
|
-
? `<div class="mp-it-secs">${it.sections.map((s) => `<div class="mp-it-sec">
|
|
8661
|
+
? `<div class="mp-it-secs">${it.sections.map((s) => `<div class="mp-it-sec" id="mp-sec-${esc(s.key)}">
|
|
7450
8662
|
<div class="mp-it-sh">${esc(s.label)}</div>
|
|
7451
8663
|
<div class="mp-it-stext">${esc(s.text)}</div>
|
|
7452
8664
|
</div>`).join('')}</div>`
|
|
@@ -7456,6 +8668,14 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
7456
8668
|
const askLine = (ask && (ask.question || (ask.direction && ask.direction !== 'general')))
|
|
7457
8669
|
? `<div class="mp-it-ask"><span class="ask-q">问题:${ask.question ? esc(ask.question) : '<span class="ask-none">(未填写)</span>'}</span><span class="ask-sep">|</span><span class="ask-d">询问方向:${esc(ask.directionLabel || '综合')}</span></div>`
|
|
7458
8670
|
: '';
|
|
8671
|
+
const answer = it.answer ? `<div class="mp-it-ans"><b>先答所问</b>${esc(it.answer)}</div>` : '';
|
|
8672
|
+
const plain = it.plain ? `<div class="mp-it-plain"><b>说人话</b>${esc(it.plain)}</div>` : '';
|
|
8673
|
+
const fu = (it.followups && it.followups.length)
|
|
8674
|
+
? `<div class="mp-it-fu"><b>还可以这样追问</b><ul>${it.followups.map((q) => `<li>${esc(q)}</li>`).join('')}</ul></div>`
|
|
8675
|
+
: '';
|
|
8676
|
+
const gloss = (it.glossary && it.glossary.length)
|
|
8677
|
+
? `<details class="mp-it-gloss"><summary>名词解释(${it.glossary.length} 条)</summary>${it.glossary.map((g) => `<div><span>${esc(g.term)}</span>${esc(g.plain)}</div>`).join('')}</details>`
|
|
8678
|
+
: '';
|
|
7459
8679
|
return `<div class="mp-it${folded ? ' folded' : ''}">
|
|
7460
8680
|
<button type="button" class="mp-it-head" aria-expanded="${folded ? 'false' : 'true'}">
|
|
7461
8681
|
<span class="mp-it-arrow" aria-hidden="true">▾</span>
|
|
@@ -7464,18 +8684,143 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
7464
8684
|
</button>
|
|
7465
8685
|
<div class="mp-it-wrap">
|
|
7466
8686
|
${askLine}
|
|
8687
|
+
${answer}
|
|
8688
|
+
${plain}
|
|
7467
8689
|
${it.lead ? `<div class="mp-it-lead">${esc(it.lead)}</div>` : ''}
|
|
7468
8690
|
${it.error ? `<div class="mp-it-err"><b>⚠ 解析生成失败</b>${esc(it.error)}——上方盘面数据本身可用</div>` : ''}
|
|
8691
|
+
${outline}
|
|
8692
|
+
${toc}
|
|
7469
8693
|
<div class="mp-it-body">${dims}</div>
|
|
7470
8694
|
${secs}
|
|
8695
|
+
${tables}
|
|
8696
|
+
${fu}
|
|
8697
|
+
${gloss}
|
|
7471
8698
|
<div class="mp-it-dis">${esc(it.disclaimer)}</div>
|
|
7472
8699
|
</div>
|
|
7473
8700
|
</div>`;
|
|
7474
8701
|
};
|
|
7475
8702
|
|
|
8703
|
+
pp.followupHTML = function (fx) {
|
|
8704
|
+
if (!fx) return '';
|
|
8705
|
+
const ev = (fx.evidence && fx.evidence.length)
|
|
8706
|
+
? `<div class="mp-fu-ev">依据:${fx.evidence.map((x) => esc(x)).join(';')} 置信度:${esc(fx.confidence || '弱')}</div>`
|
|
8707
|
+
: '';
|
|
8708
|
+
return `<div class="mp-fu${fx.ok ? '' : ' miss'}">
|
|
8709
|
+
<div class="mp-fu-h">追问 · ${esc(fx.question)}</div>
|
|
8710
|
+
<div class="mp-fu-a">${esc(fx.answer)}</div>
|
|
8711
|
+
${ev}
|
|
8712
|
+
</div>`;
|
|
8713
|
+
};
|
|
8714
|
+
|
|
7476
8715
|
pp.resultWithInterpret = function (html, it, opt) {
|
|
7477
8716
|
return (html || '') + pp.interpretHTML(it, opt);
|
|
7478
8717
|
};
|
|
8718
|
+
|
|
8719
|
+
pp.summaryCardPNG = function (r, it) {
|
|
8720
|
+
try {
|
|
8721
|
+
if (typeof document === 'undefined' || !document.createElement) return '';
|
|
8722
|
+
const outline = (it && it.outline) || [];
|
|
8723
|
+
const tables = ((it && it.tables) || []).filter((t) => Array.isArray(t.rows) && t.rows.length);
|
|
8724
|
+
const systemLabel = (it && it.systemLabel) || (r && r.system) || '命理报告';
|
|
8725
|
+
const W = 900;
|
|
8726
|
+
const PAD = 44;
|
|
8727
|
+
const line = 26;
|
|
8728
|
+
const font = (size, bold) => `${bold ? '600 ' : ''}${size}px "PingFang SC","Microsoft YaHei","Noto Sans CJK SC",sans-serif`;
|
|
8729
|
+
const canvas = document.createElement('canvas');
|
|
8730
|
+
const ctx = canvas.getContext ? canvas.getContext('2d') : null;
|
|
8731
|
+
if (!ctx) return '';
|
|
8732
|
+
const wrap = (text, maxWidth) => {
|
|
8733
|
+
const out = [];
|
|
8734
|
+
let cur = '';
|
|
8735
|
+
for (const ch of String(text)) {
|
|
8736
|
+
const test = cur + ch;
|
|
8737
|
+
if (ctx.measureText(test).width > maxWidth && cur) { out.push(cur); cur = ch; } else cur = test;
|
|
8738
|
+
}
|
|
8739
|
+
if (cur) out.push(cur);
|
|
8740
|
+
return out.length ? out : [''];
|
|
8741
|
+
};
|
|
8742
|
+
ctx.font = font(15);
|
|
8743
|
+
let bodyH = 0;
|
|
8744
|
+
const outlineBlocks = outline.map((o) => {
|
|
8745
|
+
const lines = wrap(o.text, W - PAD * 2 - 70);
|
|
8746
|
+
bodyH += Math.min(lines.length, 2) * line + 10;
|
|
8747
|
+
return { o, lines: lines.slice(0, 2) };
|
|
8748
|
+
});
|
|
8749
|
+
const table = tables[0] || null;
|
|
8750
|
+
const tableRows = table ? table.rows.slice(0, 6) : [];
|
|
8751
|
+
const tableH = table ? 54 + tableRows.length * 34 : 0;
|
|
8752
|
+
const H = 150 + bodyH + (outlineBlocks.length ? 34 : 0) + tableH + 120;
|
|
8753
|
+
|
|
8754
|
+
canvas.width = W;
|
|
8755
|
+
canvas.height = H;
|
|
8756
|
+
ctx.fillStyle = 'rgb(255,255,255)';
|
|
8757
|
+
ctx.fillRect(0, 0, W, H);
|
|
8758
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
8759
|
+
ctx.fillRect(0, 0, W, 8);
|
|
8760
|
+
|
|
8761
|
+
let y = 52;
|
|
8762
|
+
ctx.fillStyle = 'rgb(27,27,27)';
|
|
8763
|
+
ctx.font = font(28, true);
|
|
8764
|
+
ctx.fillText(`${systemLabel} · 摘要`, PAD, y);
|
|
8765
|
+
y += 30;
|
|
8766
|
+
ctx.fillStyle = 'rgb(111,106,96)';
|
|
8767
|
+
ctx.font = font(14);
|
|
8768
|
+
const meta = [r && r.input && r.input.datetime ? `出生/起局:${r.input.datetime}` : '', '本地计算 · 可复现'].filter(Boolean).join(' ');
|
|
8769
|
+
ctx.fillText(meta, PAD, y);
|
|
8770
|
+
y += 34;
|
|
8771
|
+
|
|
8772
|
+
if (outlineBlocks.length) {
|
|
8773
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
8774
|
+
ctx.font = font(16, true);
|
|
8775
|
+
ctx.fillText('一页读懂', PAD, y);
|
|
8776
|
+
y += 26;
|
|
8777
|
+
for (const { o, lines } of outlineBlocks) {
|
|
8778
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
8779
|
+
ctx.font = font(14, true);
|
|
8780
|
+
ctx.fillText(o.label, PAD, y);
|
|
8781
|
+
ctx.fillStyle = 'rgb(63,58,51)';
|
|
8782
|
+
ctx.font = font(15);
|
|
8783
|
+
lines.forEach((text, i) => ctx.fillText(text, PAD + 70, y + i * line));
|
|
8784
|
+
y += lines.length * line + 10;
|
|
8785
|
+
}
|
|
8786
|
+
y += 8;
|
|
8787
|
+
}
|
|
8788
|
+
|
|
8789
|
+
if (table) {
|
|
8790
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
8791
|
+
ctx.font = font(16, true);
|
|
8792
|
+
ctx.fillText(table.title, PAD, y);
|
|
8793
|
+
y += 24;
|
|
8794
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
8795
|
+
ctx.font = font(13);
|
|
8796
|
+
ctx.fillText(table.columns.join(' · '), PAD, y);
|
|
8797
|
+
y += 8;
|
|
8798
|
+
ctx.strokeStyle = 'rgb(229,225,216)';
|
|
8799
|
+
ctx.beginPath(); ctx.moveTo(PAD, y); ctx.lineTo(W - PAD, y); ctx.stroke();
|
|
8800
|
+
y += 22;
|
|
8801
|
+
ctx.fillStyle = 'rgb(63,58,51)';
|
|
8802
|
+
ctx.font = font(14);
|
|
8803
|
+
for (const row of tableRows) {
|
|
8804
|
+
const text = row.filter(Boolean).join(' · ');
|
|
8805
|
+
const clipped = wrap(text, W - PAD * 2)[0];
|
|
8806
|
+
ctx.fillText(clipped, PAD, y);
|
|
8807
|
+
y += 34;
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8810
|
+
|
|
8811
|
+
y = H - 62;
|
|
8812
|
+
ctx.strokeStyle = 'rgb(229,225,216)';
|
|
8813
|
+
ctx.beginPath(); ctx.moveTo(PAD, y - 18); ctx.lineTo(W - PAD, y - 18); ctx.stroke();
|
|
8814
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
8815
|
+
ctx.font = font(12);
|
|
8816
|
+
ctx.fillText('内容用于传统文化学习与自我探索,不构成医疗、投资、法律或人事决策依据。', PAD, y + 6);
|
|
8817
|
+
ctx.fillText('dsh-metaphysics · 本机计算,未上传任何数据', PAD, y + 26);
|
|
8818
|
+
|
|
8819
|
+
return canvas.toDataURL ? canvas.toDataURL('image/png') : '';
|
|
8820
|
+
} catch (e) {
|
|
8821
|
+
return '';
|
|
8822
|
+
}
|
|
8823
|
+
};
|
|
7479
8824
|
})();
|
|
7480
8825
|
|
|
7481
8826
|
const HIST_KEY = 'dsh-metaphysics/history-v1';
|
|
@@ -7560,6 +8905,11 @@ const CSS = `
|
|
|
7560
8905
|
.mp-empty b{display:block;font-size:15px;color:var(--dsw-alias-label-primary);margin-bottom:6px;font-weight:600}
|
|
7561
8906
|
.mp-note{margin-top:12px;padding:9px 14px;border-radius:10px;font-size:12px;background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 14%,transparent);color:var(--dsw-alias-state-warning-primary)}
|
|
7562
8907
|
.mp-bar{display:flex;gap:8px;align-items:center;margin-top:14px}
|
|
8908
|
+
.mp-askbox{display:flex;gap:8px;align-items:center;margin-top:12px}
|
|
8909
|
+
.mp-askbox .mp-input{flex:1;min-width:0}
|
|
8910
|
+
.mp-fx{margin-top:10px}
|
|
8911
|
+
.mp-checks{flex-direction:row;align-items:center;gap:7px;font-size:12.5px}
|
|
8912
|
+
.mp-checks input{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary)}
|
|
7563
8913
|
.mp-auto{margin:-8px 0 14px;padding-left:2px;font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary)}
|
|
7564
8914
|
.mp-cast{margin:0 0 14px;padding-left:2px;font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary)}
|
|
7565
8915
|
.mp-cast b{color:var(--dsw-alias-label-primary);font-weight:600}
|
|
@@ -7645,6 +8995,9 @@ function App() {
|
|
|
7645
8995
|
const [hpChart, setHpChart] = useState('bazi');
|
|
7646
8996
|
const [qimenRule, setQimenRule] = useState('chaibu');
|
|
7647
8997
|
const [trueSolar, setTrueSolar] = useState(true);
|
|
8998
|
+
const [crossOn, setCrossOn] = useState(false);
|
|
8999
|
+
const [askText, setAskText] = useState('');
|
|
9000
|
+
const [fx, setFx] = useState(null);
|
|
7648
9001
|
const [onlineGeo, setOnlineGeo] = useState(false);
|
|
7649
9002
|
const [lines, setLines] = useState('');
|
|
7650
9003
|
const [result, setResult] = useState(null);
|
|
@@ -7670,19 +9023,21 @@ function App() {
|
|
|
7670
9023
|
const usesTimeInput = !(tab === 'meihua' && mhMode === 'number');
|
|
7671
9024
|
const showDT = tab !== 'tarot' && usesTimeInput;
|
|
7672
9025
|
|
|
7673
|
-
const run = async (kind, override, modeOverride) => {
|
|
9026
|
+
const run = async (kind, override, modeOverride, over) => {
|
|
7674
9027
|
const k = kind || tab;
|
|
7675
9028
|
const mh = modeOverride || mhMode;
|
|
7676
9029
|
const dt = override ? override : { date, time };
|
|
9030
|
+
const genderV = over && over.gender ? over.gender : gender;
|
|
9031
|
+
const addressV = over && typeof over.address === 'string' ? over.address : address;
|
|
7677
9032
|
try {
|
|
7678
9033
|
const datetime = `${dt.date}T${dt.time}`;
|
|
7679
9034
|
const args = { system: k };
|
|
7680
9035
|
if (k !== 'tarot') args.datetime = datetime;
|
|
7681
9036
|
if (k === 'bazi' || k === 'ziwei' || k === 'hepan') {
|
|
7682
|
-
args.gender =
|
|
9037
|
+
args.gender = genderV;
|
|
7683
9038
|
args.trueSolar = trueSolar;
|
|
7684
|
-
if (
|
|
7685
|
-
args.address =
|
|
9039
|
+
if (addressV.trim()) {
|
|
9040
|
+
args.address = addressV.trim();
|
|
7686
9041
|
try {
|
|
7687
9042
|
const g = await Engine.geocodeAddress(args.address, { online: onlineGeo });
|
|
7688
9043
|
if (g && typeof g.lon === 'number') { args.longitude = g.lon; args.geo = g; }
|
|
@@ -7690,6 +9045,7 @@ function App() {
|
|
|
7690
9045
|
console.error('[metaphysics] 地址解析失败,退回内置表', e);
|
|
7691
9046
|
}
|
|
7692
9047
|
}
|
|
9048
|
+
if (k !== 'hepan') args.cross = crossOn;
|
|
7693
9049
|
}
|
|
7694
9050
|
if (k === 'liuyao') {
|
|
7695
9051
|
const parsed = Core.parseLines(lines);
|
|
@@ -7740,8 +9096,12 @@ function App() {
|
|
|
7740
9096
|
? { question: needAsk(k) ? question.trim() : '', direction }
|
|
7741
9097
|
: undefined;
|
|
7742
9098
|
const it = Engine.interpret ? Engine.interpret(r, ask) : null;
|
|
9099
|
+
if (it && r.cross && Engine.crossText) {
|
|
9100
|
+
it.sections = [...(it.sections || []), { key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: Engine.crossText(r.cross) }];
|
|
9101
|
+
}
|
|
7743
9102
|
const timed = kindOf(k) === 'cast' && (k !== 'meihua' || mh === 'time');
|
|
7744
9103
|
setResult({ kind: k, r, it, at: dt, timed });
|
|
9104
|
+
setFx(null);
|
|
7745
9105
|
setError(''); setCopied('');
|
|
7746
9106
|
if (k !== 'tarot') {
|
|
7747
9107
|
const entry = {
|
|
@@ -7804,6 +9164,17 @@ function App() {
|
|
|
7804
9164
|
run(tab);
|
|
7805
9165
|
};
|
|
7806
9166
|
|
|
9167
|
+
const openSample = () => {
|
|
9168
|
+
const sample = { date: '1990-05-12', time: '14:30' };
|
|
9169
|
+
setTab('bazi');
|
|
9170
|
+
setGender('male');
|
|
9171
|
+
setAddress('河北石家庄');
|
|
9172
|
+
setManual((m) => Object.assign({}, m, { birth: sample }));
|
|
9173
|
+
setDate(sample.date);
|
|
9174
|
+
setTime(sample.time);
|
|
9175
|
+
run('bazi', sample, null, { gender: 'male', address: '河北石家庄' });
|
|
9176
|
+
};
|
|
9177
|
+
|
|
7807
9178
|
const switchMode = (m) => {
|
|
7808
9179
|
setMhMode(m);
|
|
7809
9180
|
run(tab, { date, time }, m);
|
|
@@ -7848,6 +9219,46 @@ function App() {
|
|
|
7848
9219
|
} catch (e) { setCopied('复制失败'); }
|
|
7849
9220
|
};
|
|
7850
9221
|
|
|
9222
|
+
const doAsk = () => {
|
|
9223
|
+
if (!result || !result.r || !askText.trim()) return;
|
|
9224
|
+
try {
|
|
9225
|
+
setFx(Engine.followup ? Engine.followup(result.r, askText.trim()) : null);
|
|
9226
|
+
} catch (e) { setFx({ question: askText.trim(), ok: false, answer: '追问失败:' + (e && e.message ? e.message : e), evidence: [], confidence: '弱' }); }
|
|
9227
|
+
};
|
|
9228
|
+
|
|
9229
|
+
const downloadReport = () => {
|
|
9230
|
+
if (!result || !result.r) return;
|
|
9231
|
+
try {
|
|
9232
|
+
const text = Engine.renderText ? Engine.renderText(result.r) : (result.r.summary || '');
|
|
9233
|
+
const blob = new Blob([text], { type: 'text/markdown;charset=utf-8' });
|
|
9234
|
+
const a = document.createElement('a');
|
|
9235
|
+
a.href = URL.createObjectURL(blob);
|
|
9236
|
+
a.download = `命理报告-${result.r.system}.md`;
|
|
9237
|
+
document.body.appendChild(a);
|
|
9238
|
+
a.click();
|
|
9239
|
+
a.remove();
|
|
9240
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
|
|
9241
|
+
} catch (e) {
|
|
9242
|
+
console.error('[metaphysics] 导出失败', e);
|
|
9243
|
+
}
|
|
9244
|
+
};
|
|
9245
|
+
|
|
9246
|
+
const downloadCard = () => {
|
|
9247
|
+
if (!result || !result.r) return;
|
|
9248
|
+
try {
|
|
9249
|
+
const url = PP.summaryCardPNG ? PP.summaryCardPNG(result.r, result.it) : '';
|
|
9250
|
+
if (!url) { setCopied('当前环境不支持 Canvas 导出'); return; }
|
|
9251
|
+
const a = document.createElement('a');
|
|
9252
|
+
a.href = url;
|
|
9253
|
+
a.download = `命理摘要-${result.r.system}.png`;
|
|
9254
|
+
document.body.appendChild(a);
|
|
9255
|
+
a.click();
|
|
9256
|
+
a.remove();
|
|
9257
|
+
} catch (e) {
|
|
9258
|
+
console.error('[metaphysics] 长图导出失败', e);
|
|
9259
|
+
}
|
|
9260
|
+
};
|
|
9261
|
+
|
|
7851
9262
|
const field = (label, node) => h('label', { className: 'mp-field' }, h('span', { className: 'mp-lbl' }, label), node);
|
|
7852
9263
|
|
|
7853
9264
|
const replay = (x) => {
|
|
@@ -7901,6 +9312,12 @@ function App() {
|
|
|
7901
9312
|
className: 'mp-input', placeholder: '如 河北石家庄 / 成都', value: address,
|
|
7902
9313
|
onChange: (e) => setAddress(e.target.value),
|
|
7903
9314
|
})) : null,
|
|
9315
|
+
(tab === 'bazi' || tab === 'ziwei') ? h('label', { className: 'mp-field mp-checks' },
|
|
9316
|
+
h('input', {
|
|
9317
|
+
type: 'checkbox', checked: crossOn,
|
|
9318
|
+
onChange: (e) => setCrossOn(e.target.checked),
|
|
9319
|
+
}),
|
|
9320
|
+
h('span', { className: 'mp-lbl' }, '八字 × 紫微 交叉验证')) : null,
|
|
7904
9321
|
tab === 'hepan' ? field('对方出生日期', h('input', {
|
|
7905
9322
|
className: 'mp-input', type: 'date', value: pDate, onChange: (e) => setPDate(e.target.value),
|
|
7906
9323
|
})) : null,
|
|
@@ -7973,6 +9390,10 @@ function App() {
|
|
|
7973
9390
|
onBlur: (e) => refreshInterpret(undefined, e.target.value),
|
|
7974
9391
|
})) : null,
|
|
7975
9392
|
h('div', { key: 'actions', className: 'mp-actions' },
|
|
9393
|
+
h('button', {
|
|
9394
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginRight: 8 },
|
|
9395
|
+
onClick: openSample, title: '用固定示例盘看完整报告(不改动你已填的数据)',
|
|
9396
|
+
}, '看示例'),
|
|
7976
9397
|
h('button', { className: 'mp-btn', onClick: go, title: '时间仍为自动时,点它会取当下再起卦' },
|
|
7977
9398
|
tab === 'tarot' ? '抽牌' : tab === 'bazi' || tab === 'ziwei' ? '排盘' : tab === 'hepan' ? '合盘' : '起卦'))),
|
|
7978
9399
|
|
|
@@ -8029,7 +9450,26 @@ function App() {
|
|
|
8029
9450
|
|
|
8030
9451
|
result ? h('div', { key: 'bar', className: 'mp-bar' },
|
|
8031
9452
|
h('button', { className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px' }, onClick: copyResult },
|
|
8032
|
-
copied || '复制文字结果')
|
|
9453
|
+
copied || '复制文字结果'),
|
|
9454
|
+
h('button', {
|
|
9455
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginLeft: 8 },
|
|
9456
|
+
onClick: downloadReport, title: '导出盘面 + 解析(含一页读懂、三张表)为 Markdown',
|
|
9457
|
+
}, '导出 .md'),
|
|
9458
|
+
h('button', {
|
|
9459
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginLeft: 8 },
|
|
9460
|
+
onClick: downloadCard, title: '生成 PNG 摘要卡(一页读懂 + 首张表),适合直接分享',
|
|
9461
|
+
}, '存为摘要长图')) : null,
|
|
9462
|
+
|
|
9463
|
+
result ? h('div', { key: 'ask', className: 'mp-askbox' },
|
|
9464
|
+
h('input', {
|
|
9465
|
+
className: 'mp-input', value: askText, maxLength: 120,
|
|
9466
|
+
placeholder: '追问:如 2027 年事业怎么样(回车提交)',
|
|
9467
|
+
onChange: (e) => setAskText(e.target.value),
|
|
9468
|
+
onKeyDown: (e) => { if (e.key === 'Enter') doAsk(); },
|
|
9469
|
+
}),
|
|
9470
|
+
h('button', { className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px' }, onClick: doAsk }, '追问')) : null,
|
|
9471
|
+
|
|
9472
|
+
fx ? h('div', { key: 'fx', className: 'mp-fx', dangerouslySetInnerHTML: { __html: PP.followupHTML(fx) } }) : null,
|
|
8033
9473
|
|
|
8034
9474
|
(!result && !error) ? h('div', { key: 'ph', className: 'mp-dim', style: { marginTop: 24, fontSize: 12 } },
|
|
8035
9475
|
'选择体系后点「排盘」。所有计算在本机完成,不上传任何数据。') : null,
|